Lecture Notes in Computer Science Commenced Publication in 1973 Founding and Former Series Editors: Gerhard Goos, Juris Hartmanis, and Jan van Leeuwen
Editorial Board David Hutchison Lancaster University, UK Takeo Kanade Carnegie Mellon University, Pittsburgh, PA, USA Josef Kittler University of Surrey, Guildford, UK Jon M. Kleinberg Cornell University, Ithaca, NY, USA Alfred Kobsa University of California, Irvine, CA, USA Friedemann Mattern ETH Zurich, Switzerland John C. Mitchell Stanford University, CA, USA Moni Naor Weizmann Institute of Science, Rehovot, Israel Oscar Nierstrasz University of Bern, Switzerland C. Pandu Rangan Indian Institute of Technology, Madras, India Bernhard Steffen TU Dortmund University, Germany Madhu Sudan Microsoft Research, Cambridge, MA, USA Demetri Terzopoulos University of California, Los Angeles, CA, USA Doug Tygar University of California, Berkeley, CA, USA Gerhard Weikum Max-Planck Institute of Computer Science, Saarbruecken, Germany
6092
Lars Grunske Ralf Reussner Frantisek Plasil (Eds.)
Component-Based Software Engineering 13th International Symposium, CBSE 2010 Prague, Czech Republic, June 23-25, 2010 Proceedings
13
Volume Editors Lars Grunske Swinburne University of Technology, Faculty of ICT John Street, Hawthorn, Melbourne, VIC 3122, Australia E-mail:
[email protected] Ralf Reussner Karlsruhe Institute of Technology (KIT) Institute for Program Structures and Data Organization Am Fasanengarten 5, 76131 Karlsruhe, Germany E-mail:
[email protected] Frantisek Plasil Charles University, Department of Distributed and Dependable Systems Malostranske nam. 25, 11800 Prague, Czech Republic E-mail:
[email protected]
Library of Congress Control Number: 2010927126 CR Subject Classification (1998): D.2, F.3, D.3, C.2, C.3, D.2.4 LNCS Sublibrary: SL 2 – Programming and Software Engineering ISSN ISBN-10 ISBN-13
0302-9743 3-642-13237-5 Springer Berlin Heidelberg New York 978-3-642-13237-7 Springer Berlin Heidelberg New York
This work is subject to copyright. All rights are reserved, whether the whole or part of the material is concerned, specifically the rights of translation, reprinting, re-use of illustrations, recitation, broadcasting, reproduction on microfilms or in any other way, and storage in data banks. Duplication of this publication or parts thereof is permitted only under the provisions of the German Copyright Law of September 9, 1965, in its current version, and permission for use must always be obtained from Springer. Violations are liable to prosecution under the German Copyright Law. springer.com © Springer-Verlag Berlin Heidelberg 2010 Printed in Germany Typesetting: Camera-ready by author, data conversion by Scientific Publishing Services, Chennai, India Printed on acid-free paper 06/3180
Preface
The 2010 Symposium on Component-Based Software Engineering (CBSE 2010) was the 13th in a series of successful events that have grown into the main forum for industrial and academic experts to discuss component technology. CBSE is concerned with the development of software-intensive systems from independently developed software-building blocks (components), the development of components, and system maintenance and improvement by means of component replacement and customization. The aim of the conference is to promote a science and technology foundation for achieving predictable quality in software systems through the use of software component technology and its associated software engineering practices. In line with a broad interest, CBSE 2010 received 48 submissions. From these submissions, 14 were accepted after a careful peer-review process followed by an online program committee discussion. This resulted in an acceptance rate of 29%. The selected technical papers are published in this volume. For the fourth time, CBSE 2010 was held as part of the conference series: Federated Events on Component-Based Software Engineering and Software Architecture (COMPARCH). The federated events were: the 13th International Symposium on Component-Based Software Engineering (CBSE 2010), the 6th International Conference on the Quality of Software Architectures (QoSA 2010), and the 1st International Symposium on Architecting Critical Systems (ISARCS 2010). Together with COMPARCH’s Industrial Experience Report Track and the co-located Workshop on Component-Oriented Programming (WCOP 2010), COMPARCH provided a broad spectrum of events related to components and architectures. A new feature this year was the role of WCOP as a doctoral symposium for COMPARCH. This created a clear borderline between CBSE as a research-oriented conference with well-validated research results as contributions and WCOP as an entry-level scientific event for young researchers. Among the many people who contributed to the success of CBSE 2010, we would like to thank the members of the Program Committees for their valuable work during the review process. We also wish to thank the ACM Special Interest Group on Software Engineering (SIGSOFT) for its sponsorship. March 2010
Lars Grunske Ralf Reussner
Organization
CBSE 2010 (Part of COMPARCH 2010) Program Committee Chairs Lars Grunske Ralf Reussner
Swinburne University of Technology, Australia Karlsruhe Institute of Technology (KIT), Germany
General Chair Frantisek Plasil
Charles University, Czech Republic
Steering Committee Ivica Crnković Ian Gorton George Heineman Raffaela Mirandola Heinz Schmidt Judith Stafford Clemens Szyperski
Mälardalen University, Sweden Pacific Northwest National Lab, USA Worcester Polytechnic Institute, USA Politecnico di Milano, Italy RMIT University, Australia Tufts University, USA Microsoft, USA
Program Committee Steffen Becker Judith Bishop Barbora Buhnova Michel Chaudron Kendra Cooper Ivica Crnkovic Xavier Franch Morven Gentleman Sudipto Ghosh Holger Giese Ian Gorton Lars Grunske Richard Hall Jens Happe George Heineman Christine Hofmeister Dean Jin
Forschungszentrum Informatik (FZI), Germany Microsoft Research, Redmond, USA Masaryk University, Brno, Czech Republic Leiden University, Netherlands University of Texas at Dallas, USA Mälardalen University, Sweden Universitat Politècnica de Catalunya, Spain Dalhousie University, Canada Colorado State University, USA Hasso Plattner Institute, Potsdam, Germany Pacific North West National Laboratory, USA Swinburne University of Technology, Australia Sun Microsystems, USA Forschungszentrum Informatik (FZI), Germany Worcester Polytechnic Institute, USA East Stroudsburg University, USA University of Manitoba, Canada
VIII
Organization
Joe Kiniry Magnus Larsson Kung-Kiu Lau Grace A. Lewis Jenny Liu Michael Maximilien Marija Mikic-Rakic Raffaela Mirandola Henry Muccini Rob van Ommering Frantisek Plasil Noel Plouzeau Iman Poernomo Ralf Reussner Salah Sadou Christian Salzmann Bernhard Schätz Douglas Schmidt Jean-Guy Schneider Judith Stafford Clemens Szyperski Kurt Wallnau Dave Wile
University College Dublin, Ireland ABB AB, Sweden The University of Manchester, UK Carnegie Mellon University, USA Pacific Northwest National Laboratory, USA IBM, USA Google Inc., USA Politecnico di Milano, Italy University of L’Aquila, Italy Philips Research, Netherlands Charles University, Czech Republic IRISA - University of Rennes, France King’s College London, UK University of Karlsruhe, Germany Valoria, Université de Bretagne Sud, France BMW Group, Germany TU München, Germany Vanderbilt University, USA Swinburne University of Technology, Australia Tufts University, USA Microsoft, USA Software Engineering Institute, USA Teknowledge Corporation, USA
Co-reviewers Olivier Barais, Basil Becker, Franz Brosch, Jan Carlson, Stefan Claus, Benoit Combemale, Zoya Durdik, Clement Escoffier, Gregor Gabrysiak, Jörg Henß, Garth Heward, Thomas Klattig, Ivano Malavolta, Michal Malohlava, Josu Martinez, Stefan Neumann, Keng-Yap Ng, Azlin Nordin, Fouad Omri, Pavel Parizek, Karl Pauls, Tomas Poch, Tauseef Rana, Chris Rathfelder, Lily Safie, Robert Senger, Séverine Sentilles, Ondrej Sery, Rodrigo Vivanco, Aneta Vukgarakis, Marek Winkler.
Table of Contents
Component-Based Embedded Systems Reliability Analysis of Component-Based Systems with Multiple Failure Modes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Antonio Filieri, Carlo Ghezzi, Vincenzo Grassi, and Raffaela Mirandola Comparison of Component Frameworks for Real-Time Embedded Systems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Petr Hoˇsek, Tom´ aˇs Pop, Tom´ aˇs Bureˇs, Petr Hnˇetynka, and Michal Malohlava A Three-Tier Approach for Composition of Real-Time Embedded Software Stacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Fr´ed´eric Loiret, Lionel Seinturier, Laurence Duchien, and David Servat Bridging the Semantic Gap between Abstract Models of Embedded Systems . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Jagadish Suryadevara, Eun-Young Kang, Cristina Seceleanu, and Paul Pettersson
1
21
37
55
Component-Based Adaptive Systems Reliable Dynamic Reconfigurations in a Reflective Component Model . . . Marc L´eger, Thomas Ledoux, and Thierry Coupaye
74
Reactive Model-Based Control of Reconfiguration in the Fractal Component-Based Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Gwena¨el Delaval and Eric Rutten
93
Enabling on Demand Deployment of Middleware Services in Componentized Middleware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Yan Li, Minghui Zhou, Chao You, Guo Yang, and Hong Mei
113
A Self-healing Component Sandbox for Untrustworthy Third Party Code Execution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Kiev Gama and Didier Donsez
130
X
Table of Contents
Component Interfaces, Contracts and Adapters of Component-Based Systems Component Contracts in Eclipse - A Case Study . . . . . . . . . . . . . . . . . . . . . Jens Dietrich and Lucia Stewart
150
Automated Creation and Assessment of Component Adapters with Test Cases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Oliver Hummel and Colin Atkinson
166
An Empirical Study of the Component Dependency Resolution Search Space . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Graham Jenson, Jens Dietrich, and Hans W. Guesgen
182
Composition and (De)-composition of Component-Based Systems Component Composition Using Feature Models . . . . . . . . . . . . . . . . . . . . . . Michael Eichberg, Karl Klose, Ralf Mitschke, and Mira Mezini Restructuring Object-Oriented Applications into Component-Oriented Applications by Using Consistency with Execution Traces . . . . . . . . . . . . . Simon Allier, Houari A. Sahraoui, Salah Sadou, and St´ephane Vaucher
200
216
(Behavioural) Design Patterns as Composition Operators . . . . . . . . . . . . . Kung-Kiu Lau, Ioannis Ntalamagkas, Cuong M. Tran, and Tauseef Rana
232
Author Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
253
Reliability Analysis of Component-Based Systems with Multiple Failure Modes Antonio Filieri1 , Carlo Ghezzi1 , Vincenzo Grassi2 , and Raffaela Mirandola1 1
2
Politecnico di Milano, Piazza Leonardo Da Vinci 32, 20133 Milano, Italy {filieri,ghezzi,mirandola}@elet.polimi.it Universit` a di Roma “Tor Vergata”, Viale del Politecnico 1, 00133 Roma, Italy
[email protected]
Abstract. This paper presents a novel approach to the reliability modeling and analysis of a component-based system that allows dealing with multiple failure modes and studying the error propagation among components. The proposed model permits to specify the components attitude to produce, propagate, transform or mask different failure modes. These component-level reliability specifications together with information about systems global structure allow precise estimation of reliability properties by means of analytical closed formulas, probabilistic modelchecking or simulation methods. To support the rapid identification of components that could heavily affect systems reliability, we also show how our modeling approach easily support the automated estimation of the system sensitivity to variations in the reliability properties of its components. The results of this analysis allow system designers and developers to identify critical components where it is worth spending additional improvement efforts.
1
Introduction
In component-based (CB) systems it became quickly evident that the whole is more than the sum of its parts. Each component of the system can affect global, perceivable properties of the entire system. A crucial issue in CB development is the assessment of the quality properties of the whole system starting from the properties of its components. Methodologies to quickly predict these global properties, before the actual components integration and system release, can be used to drive the development process, by supporting architectural decisions about components assembly and giving indications about critical components that could deserve customized development efforts. In this paper, we focus on CB software systems that operate in safety-critical environments, where a relevant quality factor is the system reliability, defined as a probabilistic measure of the system ability to successfully carry out its own task. To support reliability engineering of such systems, we provide a methodology to analyze their reliability, starting from information about the reliability properties of their L. Grunske, R. Reussner, and F. Plasil (Eds.): CBSE 2010, LNCS 6092, pp. 1–20, 2010. c Springer-Verlag Berlin Heidelberg 2010
2
A. Filieri et al.
components and architectural information about how they are assembled. Using this information, we show how to get an estimate of the overall system reliability and of its sensitivity with respect to variations in the reliability properties of its components. Avizienis et al. [1] clearly described the need to deal with multiple different failure modes. A single Boolean domain (failure/no failure) is not expressive enough to represent important pathological behaviors. Moreover, in the same paper the authors also stress the importance of considering the error propagation process among the system components. Nonetheless, few modeling approaches deal with error propagation across a component-based system (e.g., [2,3,4]), and, to the best of our knowledge, none deals with multiple failure modes. On the contrary, to get a complete figure of the possible failure pathology of the whole system, our methodology takes into account that components can experience a number of different failure modes and those failures can propagate in different ways across the execution flow, possibly spreading up to the application interface. In particular, we also consider the transformation of failure modes across components. Indeed, a component invoked with an incoming failure mode, could possibly output a different failure mode. The modeling approach we propose is expressive enough to represent the failure mode transformation during its propagation through a component. The proposed approach can be applied at early stages of software design, and it can provide a fine prediction model which can drive decisions about both architectural and behavioral aspects. The underlying model is also suitable for sensitivity analysis that establishes how much the global system reliability (for each failure mode) depends upon each model parameter. Specifically, not all the components have the same importance with respect to global reliability, and improvements in the reliability of certain components produce a larger impact on the improvement of the global systems reliability. Besides estimating the system sensitivity, we propose a method to find the optimal combination of component reliability properties values, that maximizes, for example, the system reliability (possibly, under constraints related to the cost of reliability improvements for different components). This gives an additional support to design and development decisions: for example, it could lead to prefer slightly less reliable but cheaper components, with respect to more reliable versions. Furthermore, using the optimization results it is possible to obtain the best combination of values to look for in component selection. The paper is organized as follows. In Section 2 we introduce the component and CB architecture models, suitable to describe multiple failure modes and error propagation. In Section 3 we show how to build a Markov model from these component and architecture models, while in Section 4 we sketch some useful analysis techniques, in order to make the most of the information just modeled. In Section 5, we show through a simple example the practical application of the presented ideas. In Section 6 we briefly review related work and finally, Section 7 concludes the paper.
Reliability Analysis of CB Systems with Multiple Failure Modes
2 2.1
3
System Model Basic Concepts
According to [1], a failure is defined as a deviation of the service delivered by a system from the correct service. An error is the part of the system state that could lead to the occurrence of a failure, and is caused by the activation of a fault. The deviation from correct service can be manifested in different ways, corresponding to different failure modes of the system. Characterizing the failure modes that may occur in a system is, in general, a system-dependent activity. Two basic failure modes that can be identified are, for example: content and timing failures (where, respectively, content of system’s output and delivery time deviate from the correct ones). Other failure modes could be defined, for example, by grading basic modes’ severity, or by combining them (e.g., content and timing simoultaneously). Special failure modes, when both timing and content are incorrect, are halt failures; these make system activity, if any, no longer perceptible at the system interface. Errors can arise both because of silent internal fault, or because of an erroneus input received through its interface. However, errors in a component not necessarily manifest themselves as component failures. In turn, component failures not necessarily imply system failures. A component failure occurs only when an error propagates within the component up to its interface, and a system failure occurs only when an error propagates through components up to the system interface. In this propagation path, an error can get masked, e.g., when an erroneous value is overwritten before being delivered to the interface. An error can also get transformed, e.g., content failure received from another component may cause additional computations, leading to the occurrence of a timing failure. To analyze the reliability of a component-based system, we should take into account the whole set of factors outlined above, that can lead to the manifestation of a failure. At component level, this requires to estimate the likelihood that a given failure mode manifests itself at the component interface because of an internal fault, or by the propagation of the same (or different) failure mode received at the component input interface. At system level, we should consider the possible propagation paths through components, and their respective likelihood. In the next subsection, we present a reliability model for component-based systems that provides a representation of this information. 2.2
Reliability Model of Component-Based System
It is well understood that, to support component-based development, each component should be equipped with information about its functional properties that permit it to interact with other components. This information includes, for example, a specification of the services required or provided by the component, and this is often referred to as the component constructive interface [5]. Several component models have been proposed in the recent past [6], characterized by slightly different definitions of the constructive interface.
4
A. Filieri et al.
To support reasoning about nonfunctional properties like reliability, additional information should be provided, expressed through a suitable analytic interface. The model presented in this section defines a reliability-oriented analytic interface. In this model, we assume that each component (and hence the system built from those components) is characterized by N different failure modes. Each mode r, (1 ≤ r ≤ N ) could be one of the basic modes outlined in the previous subsection, or a combination of some of them, or any other special purpose failure mode. For the sake of uniformity, we also introduce an additional failure mode (failure mode 0), which corresponds to the delivery of a correct service. Component model. A component Ci is modeled as: – an input port ipi ; – a set of output ports Oi ={opik }; – an operational model defined by the probabilities: pi (k) (∀opik ∈ Oi ), where each pi (k) is defined as: pi (k) = P r{Ci produces an output on port opik ∈ Oi |Ci received an input on its input port} It holds: opik ∈Oi pi (k) = 1; – a failure model defined by the probabilities: fi (r, s)(0 ≤ r ≤ N, 0 ≤ s ≤ N ), where each fi (r, s) is defined as: fi (r, s) = P r{Ci produces an output with failure mode s|Ci received an input with failure mode r} N It holds: s=0 fi (r, s) = 1 In this model, it is intended that transfer of both data and control takes place through the input and output ports: Ci receives data and control (activation stimuli) through its input port, and produces data and activation stimuli (control transfers) towards other components through its output ports. The operational model gives a stochastic characterization of the usage profile of other components when Ci is active. Each pi (k) probability can be interpreted as the fraction of all the data and control transfers that take place through the output port opik of Ci , over all the data and control transfers generated by Ci . Analogously, the failure model gives a stochastic characterization of the failure pathology of Ci . Figure 1 presents a graphical representation of component model’s parameters. The fi (r, s) probabilities can be used as a basis to define interesting reliability properties of a software component. Some examples of these properties are proposed in the following: – Internal failure probability with respect to failure mode s, s > 0, is the probability fi (0, s). – Robustness with respect to error mode r (r > 0, i.e. not correct) is the probability fi (r, 0). – Susceptibility with respect to error mode r (r > 0) is the probability 1 − fi (r, 0).
Reliability Analysis of CB Systems with Multiple Failure Modes
pi(1)
pi(2) Ci (a)
fi(r, s1)
r
s1
s2
fi(r, s2)
r
5
Ci
Ci (b)
Fig. 1. Component model: (a) probabilistic transfer of control from input port to output port, and (b) probabilistic propagation of failure mode r from input port to output port
– Proclivity with respect to failure mode s is the probability where βr is the probability of receiving an input mode r.
r
βr · fi (r, s),
These formal, human-understandable properties allow the easy formalization of requirements on single components together with easy-to-understand feedbacks to developers. Finally, we point out that, with respect to component models whose constructive interface specifies multiple input ports for each component, this analytic interface definition can be interpreted in two ways: – It abstracts from the actual presence of multiple input ports in a real component, by collapsing them into a single port. From this viewpoint, both the operational and failure model of this abstract component represents a sort of average profile of the real component behavior. – It actually corresponds to a projection of a real component with respect to one of its input ports. From this viewpoint, a real component is modeled by a set of these abstract components, where each element of the set models the real component behavior conditioned on having received control and data through a specific input port. For the sake of simplicity, in the following we will always use the term ”component”, without specifying according to which of these two viewpoints it should be interpreted. Architecture model. An architecture A is modeled as: – a set of components: C = {C0 , C1 , ...CM } with their analytic interfaces; M M – a mapping : mapA : i=0 Oi → i=0 {ipi }. Given an output port opik of a component Ci , mapA (opik ) defines which input port the output port opik is attached to. In this architecture definition, C1 , C2 , . . . CM−1 are intended to correspond to components used to build the application modeled by A. C0 and CM play instead a special role. They are fictitious components used to model the start of the application modeled by A and the final result produced by the application. C0 has as many output ports as the possible entry points of the application
6
A. Filieri et al.
modeled by A. Moreover, the C0 input port is not connected to any of the output ports of the A components. CM has only one input port, and no output port. Given an output port opok ∈ O0 , mapA (op0k ) = ipi means that Ci is a possible component from which the application starts its execution. Analogously, given an output port opik ∈ Oi (1 ≤ i ≤ M − 1), mapA (op0k ) = ipM means that Ci is a possible component that produces the final result of the application. The operational model associated with C0 , (given by the probabilities p0 (k) s) can thus be used to model the stochastic uncertainty about the application entry point and the user failure profile. The application termination is instead modeled by the occurrence of a transfer of control to CM . Given the special nature of C0 and CM , their failure model is defined as: f0 (r, r) = fM (r, r) = 1 (0 ≤ r ≤ N ), f0 (r, s) = fM (r, s) = 0 (0 ≤ r ≤ N, 0 ≤ s ≤ N, r = s), which means that C0 and CM do not modify the failure modes they receive. Let us define the following architecture level probabilities: FA (r, s)(0 ≤ r ≤ N, 0 ≤ s ≤ N ), where each AA (r, s) is defined as: FA (r, s) = P r{A terminates with failure mode s|A has been activated with failure mode r} Similar to the component-level properties defined above, we can use the FA (r, s) probabilities as a basis to define application-level reliability properties, such as: – Reliability, is the probability FA (0, 0). – Robustness with respect to error mode r (r > 0, i.e. not correct) is the probability FA (r, 0). – Susceptibility with respect to error mode r (r > 0) is the probability 1 − FA (r, 0). – Proclivity with respect to failure mode s is the probability r βr · FA (r, s), where βr is the probability of receiving an input mode r The component and architecture models presented above allow the definition of a reliability-oriented abstract view of a CB system that provides the starting point for our analysis methodology. This view corresponds to what is referred to in [5] as a analytic assembly of components. To carry out reliability analysis, a constructive assembly of actual software components should be mapped to this analytic assembly though a suitable analytic interpretation. It is our opinion that defining this analytic interpretation is quite easy for most of the existing component models. However, explicitly defining it for some component model is beyond the scope of this paper.
3
Markov Model of System Behavior
In this section, we show how, given a set of components C = C0 , C1 , ...CM with their respective analytic interfaces and an architecture model A as defined in the previous section, we can build a discrete time Markov process (DTMC) modeling the overall system behavior. This model can then be used to analyze reliability
Reliability Analysis of CB Systems with Multiple Failure Modes
7
properties of the system. For the sake of clarity, we split in two steps the DTMC construction: as first step, we build a DTMC providing an abstract model of the system execution process. Then we expand this DTMC into another DTMC that also includes information about the failure occurrence and propagation. Execution process model. We build a DTMC GA with state space: NGA = {c0 , c1 , . . . cM } where each state ci corresponds to a components Ci used in the A definition, and represents the system state where component Ci has received control and data from some other component. A state transition from ci models a transfer of data and control to some other component. The transition probabilities qGA (i, j) from state ci to state cj (0 ≤ i < M ), (0 < j ≤ M ) are defined as follows. Let us define the subset Oi (A, j) ⊆ Oi : Oi (A, j) = {opik |mapA (opik ) ∈ Ij }. Hence, Oi (A, j) is the subset of the output ports of Ci connected to the input port of Cj in the architecture A. Given the definition of Oi (A, j), we calculate the probabilities qA (i, j) as follows: qA (i, j) = opik ∈Oi (j) pi (k) Each qA (i, j) represents the total fraction of data and control transfers through any port from Ci to Cj , over the total number of data and control transfers from Ci . We point out that qA (i, j) > 0 if and only if Oi (A, j) = ∅. Given these definitions, c0 is the initial state of GA since, by construction, there is no transition entering this state, and cM is the final absorbing state of GA . Finally, we point out that, for the same set of components with their associated failure and operational models, different ways of connecting them (i.e., different architectures) lead to the definition of different GA ’s (see figure 2 for two different architectures A1 and A2). Failure and execution process model. GA only models the execution process as a sequence of data and control transfers among the system components, without considering the possible occurrence and propagation of errors (in other words, it models the behavior of a failure-free system). To include also this aspect, we build from GA a new DTMC HA . The state space of HA consists of a set NHA , defined as follows. First, for each ci ∈ NGA we build two sets: IMi = {imi0 , imi1 , · · · , imiN } and OMi = {omi0 , omi1 , · · · , omiN }. An element imir ∈ IMi represents the system state where component Ci has received data and control from another component with error mode r (0 ≤ r ≤ N ). An element omir ∈ OMi represents the system state where component Ci is going to transfer data and control to other components with failure mode s (0 ≤ s ≤ N ). Then, HA ’state space is: M NHA = i=0 (IMi OMi ) Hence, each state ci ∈ NGA can be seen as a ”macro-state” that is expanded into 2(N + 1) states in NHA to take into account both the execution and the error propagation processes (see fig. 3 for the case N = 2). The transition probabilities pA (x, y)(x ∈ NHA , y ∈ NHA ) are defined as follows:
8
A. Filieri et al.
Fig. 2. Mapping from the execution process model to DTMC
Ci fi(0,0)
I0
O0
fi(0,2)
ip1
Ci
op11 I1
O1
fi(1,2)
fi(2,2) I2
O2
Fig. 3. Component’s macro-state expansion
1. pA (x, y) = fi (r, s) if x = imir , y = omis (imir ∈ IMi , omis ∈ OMi , 0 ≤ i ≤ M, 0 ≤ r, s ≤ N ); 2. pA (x, y) = qA (i, j) if x = omis , y = imjs (omis ∈ OMi , imjs ∈ IMj , 0 ≤ i, j ≤ M, 0 ≤ s ≤ N ); 3. pA (x, x) = 1 if if x = imMs (imMs ∈ IMM ); 4. pA (x, y) = 0 otherwise. Case 1 above corresponds to a transition occurring within the same macro-state, and models how a given component Ci propagates to its output a failure mode received at its input, according to the failure model specified in the Ci analytic interface. We point out that we are able to represent both the case where a failure mode r is propagated ”as is” (when fi (r, r) = 1), and the case where it is transformed into another mode (when fi (r, r) < 1). Case 2 corresponds to a transition occurring between two different macro-states, and models the control and error transfer process from a component Ci to a component Cj : if a failure mode
Reliability Analysis of CB Systems with Multiple Failure Modes
9
s manifests itself at the Ci output interface, it is transferred to the input interface of Cj according to the qA (i, j) probability (which has been calculated from the operational model specified in the Ci analytic interface, and the definition of the architecture A). Case 3 corresponds to the definition of the set of states IMM as the set of final absorbing states of the DTMC HA : thus, entering a state imMs ∈ IMM models the application termination with failure mode s. We also remark that the set IM0 = {im00 , im01 , · · · , im0N } corresponds to the set of the initial states of HA as, by construction, no transition is possible towards these states: starting from a state im0r ∈ IM0 models the application activation with failure mode r. 3.1
Modeling Issues
Both the operational and failure models are based on the assumption that system’s execution respects the Markov property (which in turn implies that components’ failures are independent). In practical terms this means that each time control and data are transferred to Ci from other components, the Ci operational and failure behaviors are independent of its past history. The Markovian assumption is a limitation to the application scope of our approach. Nevertheless, many real-life applications have been proved to respect the Markov assumption, from business transaction and telephone switching systems, to the basis memory management strategies [7]. The Markovian issue is deeper treated in [8], where it is also recalled that an higher order Markov chain, the one in which the next execution step depends non only on the last but on the previous n steps, can be mapped to a first order Markov chain. Thus, our approach can be adapted to any finite order Markov chain, increasing the applicability horizon of the methodology to a large number of real-life systems. Another interesting issue related to the defined reliability properties concerns how to estimate fi (r, s). The problem can be splitted in two phases. The first phase concerns observability: to measure e fi (r, s), we must be able to identify error modes r and s. Identification can be based both on code instrumentation or on communication channels monitoring or in any other effective ad-hoc way [8]. The second phase concerns how to obtain parameters estimation. There is not an always valid methodology to face the problem. Most of the approaches are based on setting up tests in order to obtain a statistically significant amount of measurements upon which parameters estimation can be based [9,10]. In some case it is possible to shorten the testing time by adopting accelerated testing procedures, which are able to stress the most interesting parts and aspects of a system execution in order to obtain a large amount of data in a short testing time [11]. After getting measurements, the next step is the estimation of reliability parameters. This issue is more related to Statistics, even if the ability of embedding some Software Engineering knowledge in the process of sampling and estimation produces better results [12]. For the sake of completeness, there are also cases where it could be infeasible to quantify software reliability properties because of the nature of system runs which, for example, may be too long to allow the execution of a large enough test set [13]. As a final remark,
10
A. Filieri et al.
components reuse may allow the exploitation of historical data upon which reliability properties estimation can be based.
4
Analysis
The transition matrix PA = [pA (x, y)] associated with the DTMC HA constructed in Section 2 represents the probability of moving in one step from state x ∈ NHA to state y ∈ NHA . It has the following canonical form: QA RA PA = 0 I The submatrix QA is a square matrix representing one-step transitions among the HA transient states only. The submatrix RA represents instead the one-step transitions from these transient states to the set of absorbing states IMM = {imM0 , imM1 , · · · , imMN }. I is an identity matrix of size (N + 1) × (N + 1). Let us now define the matrix VA = [vA (x, y)] (x ∈ (NGA − IMM , y ∈ IMM ), whose vA (x, y) entry is defined as: vA (x, y) = P r{HA is absorbed in state y|HA starts in state x}. Given the meaning of the states in HA , we can readily see that the applicationlevel reliability properties defined in Section 2 can be expressed in terms of the VA matrix, since it holds: FA (r, s) = vA (x, y), with x = im0r and y = imMs . In the following, we show how to calculate the VA matrix from PA matrix, which allows to calculate the application-level reliability properties. Moreover, we also show how to carry out further sensitivity and optimization analysis. From the DTMC theory and matrix calculus [14], we know that matrix VA can be calculated as: VA = WA RA where WA = (I − QA )−1 . We remark that component-level reliability properties (i.e., fi (r, s) probabilities) correspond to specific entries of the QA matrix, and hence directly affect WA values. Also, by construction, the matrix RA is independent from these componentlevel properties, thus in order to establish a relation between application-level and component-level properties it suffices to study the matrix WA . WA is the inverse of the matrix I − QA , hence from linear algebra [15]: wA (x, y) = (−1)x+y
|My,x | |I − QA |
where |My,x | is the minor of (I − QA )(y, x) and |I − QA | is the determinant of I − QA . Let us give a look at matrices I − QA and My,x . First of all, My,x is obtained from I − QA by removing the y-th row and the x-th column. Thus its structure is quite similar to the one of I − QA , just omitting one row and one column. = y and j = x) represents An entry (I − QA )(i, j) (as well as My,x(i, j) when i the probability of moving from state i to state j of the DTMC. We recall that each state models either entering or leaving a component with a given failure
Reliability Analysis of CB Systems with Multiple Failure Modes
11
mode. Let i correspond to the state when the system enters component Ck with incoming failure mode r). Entries on row i of I −QA (and My,x , when applicable) will be all zeros, but a small set of them. Namely, each entry (I − QA )(i, j) will possibly be not null if and only if either i = j (because of the identity matrix I) or j corresponds to the state where component Ck , invoked with incoming failure mode r, is producing an output failure mode s: (I − QA )(i, j) = fk (r, s). We want to exploit this structure of the matrices I − QA and My,x to make explicit the relation between elements (I − QA )(i, j) and component-level reliability properties fi (r, s)’s. Due to the previously mentioned fact that matrix RA is independent of the fi (r, s)’s, we will be able to extend this relation to the system-level properties FA (r, s) easily. To this end we compute the numerator and denominator determinants through Laplace expansion with respect to the row corresponding to the set of properties fk (r, s). For matrix I − QA we obtain an expressions like the following one: det(I − QA ) = (I − QA )(i, j) · αij j
where αij represents the cofactor of the entry (I − QA )(i, j). The same procedure can be applied to matrix My,x. Due to the I − QA ’s and My,x ’s structure discussed above, we get: det(I−QA ) = f unc(fk (r, s1 ), fk (r, s2 )...) and det(My,x ) = f unction(fk (r, s1 ), fk (r, s2 )...). Thus the elements of WA can be redefined as well as function of the parameters fk (r, s)’s. Thanks to the fact that FA (r, s) is a function of WA , we are able to formalize the system level reliability properties as function of any set of component-level properties. In the following, we focus on the system reliability, defined as FA (0, 0). Sensitivity Analysis and Optimization. To determine which property most heavily affect the global reliability, we compute its sensitivity. It corresponds to the partial derivative of the function FA (0, 0) with respect to each local property of each component Ci ∂FA (0, 0) ∂fi (r, s) Besides estimating a sensitivity index, expressing explicitly FA (0, 0) as a function of the fk (r, s) parameters, allows finding the optimal combination of components reliability properties’ values. Indeed, it could happen that under given design and development constraints, it could be better to set some fi (r, s) to a value less than the trivial one (i.e., fi (0, 0) = 1). In this respect, we remark that, due to the geometry of the transition matrix representing system’s behavior, it is possible to reiterate the Laplace expansion for the computation of more cofactors. This leads to a representation of the reliability function where the dependency on interesting component-level properties is made explicit. Such a function has the shape of a fraction of polynomials and can be considered as the objective function to be maximized in a non-linear constrained optimization.
12
A. Filieri et al.
The set of constraints to be applied has to include, but not to be limited to, making all the complementary probabilities sum up to 1. Any other special purpose constraint can be added, e.g. expressing the fact that over certain thresholds some properties could be too expensive to be obtained. Also, the objective function can be extended to cope with other design goals. For example development costs can be used as coefficient to be applied to certain properties in order to make their growth more or less likely. Alternatively, the optimization problem can be restated as a multiobjective optimization, maximizing reliability and minimizing a related cost function. Trade-off between reliability improvement and development costs is an open problem. Special situations may require ad-hoc estimations for costs and their fitness to reality is mainly based on architects’ experience. To see some examples of generalized cost functions related to software reliability issues refer to [16]. A typical optimization problem to find the best combination of reliability property values looks like this: ⎧ FA (0, 0) ⎨ max subject to probability constraints ⎩ subject to design constraints Performing sensitivity analysis and determining optimal property set can be very valuable. The former can be mainly applied to produce developer feedbacks: if A (0,0) the largest value of ∂F ∂fi (r,s) is referred to the property f1 (1, 0) of component C1 , the feedback to the developer is the advise: ”Increase C1 ’s Robustness with respect to incoming error mode 1”. Thanks to the user-friendly formalization of reliability properties proposed in Section 2, any advice can be explained to the developer without the need to significantly improve her/his skills in mathematical probability, thus improving the learning curve of the development team. By using the optimization results, the best combination of reliability property values can be used as a target to look for in component selection by both a human designer or a self-adaptive system. For this purpose, the introduction of a proper distance metrics based on the set of interesting local properties can allow an automatic ranking of all the possible alternatives, making fast and easier picking the best available choice.
5
Example Application
In this section we present a short proof of concept to show an application of our novel methodology. We use for this purpose a small system consisting of three components. The first component (C1 ) plays the role of dispatcher for all the incoming requests, and is the only entry point of the system. The dispatcher analyzes the incoming requests and sends them to the server (C2 ). Depending on the specific operation requested, the server can accomplish the job by itself or it can issue some requests for more operations. In the first case, server’s outputs have to pass through a service guard (C3 ) before reaching the user. In
Reliability Analysis of CB Systems with Multiple Failure Modes
13
Fig. 4. Example application’s architecture
Fig. 5. Markov process derived from the example application
the second case, server’s requests get forwarded again to the dispatcher in order to be scheduled for execution. The server guard has the task to analyze server’s output to ensure it does not carry any confidential detail: if the guard notices something illegal, it sends back the job to the dispatcher to be processed again, otherwise it delivers the result to the user. The architecture of the system is sketched in Figure 4. We recall that in our model a transition stands for the transfer of control and data from a component to another. The probabilities expressing the operational model of each component are shown in bold face font in correspondence of the directional arrow of each connection. In this example we consider only two failure modes (denoted by 1 and 2, respectively), beside the correct execution (failure mode 0), in order to keep it simple. A partial view (without all the parameters) of the derived Markov process is represented in Figure 5. Vertical dashed lines were added as virtual delimiters between local and global execution, that is, the area entitled Cx represents the failure process ongoing while Cx holds the control. Arrows across delimiters represents the transfer of control between components. To keep it readable, only some parameters discussed in Section 3 are placed on the graph coherently with the mapping procedure explained in Section 2. The special node before C0 added to the DTMC HA models the expected profile of starting failure modes for the system activation. The probability given to each starting failure mode is also shown in figure.
14
A. Filieri et al.
In order to analyze the impact of components’ reliability properties on the global system reliability, let us firstly specify the component-level properties which are retained relevant for the system under exam: – Reliability (R): the probability that the component Ci does not produce any erroneous output, given it was invoked without incoming errors (fi (0, 0)). – Robustness (with respect to error mode r, Br ): the probability that the component Ci , invoked with incoming failure mode r, will produce a correct output (fi (r, 0)). – Internal Failure (with mode s, Fs ): the probability that the component Ci , invoked without any erroneous incoming pattern, will produce an erroneous output pattern with mode s (fi (0, s)). – Switching (from mode r to mode s with r, s > 0, Srs ): the probability that the component Ci will produce an outgoing failure mode s, given an incoming error mode s (fi (r, s)). Reliability estimation. In table 1 we show an estimation of the overall reliability (last row of the table), for three different set of values assigned to the reliability properties of each component of the system (corresponding to columns Initial, Cost 1000, Cost 1200 ). Column Initial refers to an initial attribution of values to the component parameters. The other two columns report values calculated by optimization analysis, as explained below. The reliability values were computed by means of the formula explained in section 4 and validated by simulation. Sensitivity analysis. As explained in Section 4, sensitivity analysis is a good mean to identify where to enforce improvement effort in order to obtain the larger growth of the overall reliability. Sensitivity analysis is an established method to evaluate the impact of local properties on the global system [17,2]. Nevertheless, thanks to the fact that our methodology can deal with multiple error-modes and their propagation and transformation, a designer can obtain finer information not only about where to operate refinement, but also what and how he/she has to improve. Indeed, our methodology allows to estimate the sensitivity to a specific failure transformation or propagation as well as the the internal failure probability, thus obtaining accurate results on every aspect of a CB system. However, we point out that results of sensitivity analysis should be carefully considered, when we differentiate with respect to multiple correlated parameters fi (r, s) (we recall that s fi (r, s) = 1). In this case suggested variations of a parameter imply a change in the correlated ones, and this could affect the final result in different ways. Thus sensitivity results have to be considered as useful advices to be considered by a human expert. The column labeled by Sensitivity in table 1 shows results of sensitivity analysis in our example, calculated around the initial set of parameter values (column Initial ). We note that the only component that directly delivers outputs to the user is the service guard C3 . The overall system reliability has the highest sensitivity with respect to f3 (r, 0) for all the possible r. This is not at all surprisingly
Reliability Analysis of CB Systems with Multiple Failure Modes
15
in this small example, but it could be very useful in large systems. Also notice that the 80% of the incoming requests reaches the dispatcher without any error pattern, thus the set of properties related to this situation was expected to have an high sensitivity index as it is. Optimal configuration. A novel contribution of this paper is the possibility to identify in a automatic way the best values for component-level reliability properties in order to improve the overall reliability. These values can be useful in setting design plans and goals, or even in self-adaptive component-based systems where components must be selected to maximize the system QoS, in this case reliability. In this latter case, it could be effective to have referential goal values during the selection process. To make not trivial the optimization of the model parameters, some constraints must be introduced. They could come, for example, from component availability on the market, as it could be unreasonable, in general, to ask for a component that will never fail. Other constraints could come from cost considerations. Component quality is expensive and component price could be a function more or less steep of some of its parameters. Other special purpose constraints could be in place for specific contexts. In this example we introduce the following two constraints: – Cost. Each property has its own improvement cost function. Let x represents any component’s property, in this example we set a ”price” of 800x3 for all Table 1. Analysis Results Component C1 C1 C1 C1 C1 C1 C1 C1 C1 C2 C2 C2 C2 C2 C2 C2 C2 C2 C3 C3 C3 C3 C3 C3 C3 C3 C3 Reliability
Property R F1 F2 B1 S11 S12 B2 S21 S22 R F1 F2 B1 S11 S12 B2 S21 S22 R F1 F2 B1 S11 S12 B2 S21 S22
Initial 0.94 0.03 0.03 0.05 0.92 0.03 0.70 0.00 0.30 0.64 0.13 0.23 0.03 0.84 0.13 0.03 0.13 0.84 0.96 0.02 0.02 0.65 0.169 0.181 0.05 0.336 0.614 0.6163
Sensitivity 3.3089 3.1958 3.0325 2.5692 2.4813 2.3546 1.8937 1.8290 1.7356 4.4781 4.4404 4.1232 2.5620 2.3960 2.2248 0.7744 0.7242 0.6725 0.4484 0.0583 0.0588 0.4100 0.0533 0.0538 0.2965 0.0386 0.0389
Cost 1000 0.2595 0.3014 0.4390 0.4306 0.2851 0.2842 0.2955 0.3527 0.3518 0.2020 0.4350 0.3630 0.2609 0.3696 0.3694 0.2961 0.3147 0.3891 0.7727 0.0883 0.1391 0.8649 0.0364 0.0988 0.8660 0.0358 0.0982 0.8419
Cost 1200 0.2647 0.3002 0.4351 0.4261 0.2873 0.2866 0.2936 0.3535 0.3529 0.2041 0.4320 0.3639 0.2350 0.3825 0.3824 0.2730 0.3288 0.3983 0.8240 0.0834 0.0927 0.9035 0.0391 0.0575 0.9043 0.0386 0.0570 0.8849
16
A. Filieri et al.
the component’s reliability properties, 500x5 for all the robustness properties and 200x3 for all the switching properties. This means that, for example, robustness is cheaper than reliability in the worst case, but the effective cost of robustness grows quite faster approaching higher values of x. – Quality of service. We require that the system will not terminate with failure mode 1 for more than the 5% of the requests for the considered profile of starting failure modes (i.e., we require that for failure mode 1 the proclivity property defined in Section 2 takes a value less than 5%). Even for the small system considered in this example, optimization results are not trivial to guess. In table 1 we show optimization results for global cost up to 1000 and 1200 cost units.
6
Related Work
To the best of our knowledge, no other paper have tackled the issue of stochastic analysis of reliability for CB systems, taking into account multiple failure modes and their propagation inside the system. Nevertheless there are a number of works strongly related to this. In the following we present a selection of related works to show on what our solution stands, and which is the starting point of this research. We classify the papers according to their topic as: architecture-based reliability analysis and error propagation analysis. Architecture-based reliability analysis. Surveys on architecture-based reliability analysis can be found in [18,8]. However, albeit error propagation is an important element in the chain that leads to a system failure, all existing approaches ignore it. In these approaches, the only considered parameters are the internal failure probability of each component and the interaction probabilities, with the underlying assumption that any error that arises in a component immediately manifest itself as an application failure, or equivalently that it always propagates (i.e. with probability one) up to the application outputs. Hereafter, we shortly describe some of the works that mostly influenced the proposed/adopted solution. One of the first approaches to reliability that takes distance from debugging has been proposed in 1980 [7]. The approach got named from user-oriented reliability, which is defined as the probability that the program will give the correct output with a typical set of input data from the execution environment. The user-oriented approach is now the more widely adopted and it justifies the adoption of probabilistic methods as long as the system reliability depends on the probability that a fault gets activated during a run. The reliability of a system is computed as a function of both the reliability of its components and their frequency distribution of utilization, where the system is described by as a set of interacting modules which evolves as a stochastic Markov Process and the usage frequencies can be obtained from the structural description. In [19] the authors explore the possibility of transforming architecture expressed in three popular
Reliability Analysis of CB Systems with Multiple Failure Modes
17
architectural styles into discrete Markov chains to be then analyzed by means of the approach proposed in [7]. Parameterized specification contracts, usage profile and reliability of required components as constituent factors for reliability analysis have been presented in [20]. Specifically, they consider components reliability as a combination of internal constant factors, such as reliability of the method body code, and variable factors, such as the reliability of external method calls. An approach for automatic reliability estimation in the context of self-assembling service-oriented computing taking into account relevant issues like compositionality and dependency on external resources has been proposed in [21]. Error propagation analysis. The concept of error propagation probability as the probability that an error, arising somewhere in the system, propagates through components, possibly up to the user interfaces has been introduced in [2]. The methodology in [2] assumes a single failure mode and provides tools to analyze how sensible the system is with respect to both failure and error propagation probability of each of its components. In [3], the authors proposed a notion of error permeability for modules as a basic characterization of modules’ attitude to propagate errors. Also in this case, a single, non-halting failure mode is considered. Moreover, it is proposed a method for the identification of which modules are more likely exposed to propagated errors and which modules more likely produce severe consequences on the global system, considering the propagation path of their own failure. In [22,23,24] approaches based on fault injection to estimate the error propagation characteristics of a software system during testing are presented. In the context of safety some works exist dealing with multiple failure modes, see for example [25]. However they don’t present any kind of stochastic analysis but only an examination of their possible propagation patterns. With regard to the estimate of the propagation path probabilities, the basic information exploited by all the architecture-based methodologies is the probability that component i directly interacts with component j. At early design stages, where only models of the system are available, this information can be derived from software artifacts (e.g. UML interaction diagrams), possibly annotated with probabilistic data about the possible execution and interaction patterns [26]. A review and discussion of methodologies for the interaction probability estimate can be found in [8]. A more recent method has been discussed in [27], where a Hidden Markov model is used to cope with the imperfect knowledge about the component behavior. Once the interaction probabilities are known, the probability of the different error propagation paths can be estimated under the assumption that errors propagate through component interactions. An important advantage of architectural analysis of reliability is the possibility of studying the sensitivity of the system reliability to the reliability of each component, as said in the Introduction. Although this advantage is widely recognized (e.g., [28]), few model-based approaches for computing the sensitivity of the system reliability with respect to each component reliability have been developed [7,17]. A basic work for the sensitivity analysis of the reliability with respect to some system parameter was presented in [29], but it does not
18
A. Filieri et al.
address specifically architectural issues. Moreover, all these models do not take into account the error propagation attribute and different failure modes.
7
Conclusions
In this paper we presented a novel approach to the reliability modeling and analysis of a component-based system that allows dealing with multiple failure modes and studying the error propagation among components. To support the rapid identification of components that could heavily affect system’s reliability, we have also shown how our modeling approach can easily support the automated estimation of the system sensitivity to variations in the reliability properties of its components. Furthermore, we proposed a method to find the optimal combination of component reliability properties’ values, that maximizes, for example, the system reliability. The results of these analyses support the design and development decisions in the identification of the critical components for the overall system reliability. The methodology proposed in this paper can be extended along several directions. A first direction concerns supporting the integration of our approach in the software development process. To this end, a mapping should be defined between the constructive interfaces of a component model and the analytic interfaces defined in our methodology, possibly using automated model-driven techniques. We also plan to extend our model to be able to deal with both black-box and white-box components. A second direction concerns the overcoming of some of the modeling limitations of our methodology. A first limitation comes from the underlying assumption of a sequential execution model, where control and data can be transferred from one component to another one, but not to many components. Currently, this does not allow modeling applications with parallel execution patterns. We are working towards an extension of our approach to deal also with this kind of patterns. Another possible limitation comes from the assumption of the Markov property for the component behavior. We have discussed this issue in section 3.1. Anyway, we are planning to investigate in real experiments the degree of approximation introduced by this assumption. We are also planning to investigate to what extent the approximation can be improved by introducing in our model some kind of dependence on past history, as outlined in section 3.1. Finally, we are aware that the effectiveness of the proposed approach should be assessed by an empirical validation, and we are planning for this purpose a comprehensive set of real experiments.
Acknowledgments Work partially supported by the Italian PRIN project D-ASAP and by the EU projects Q-ImPrESS (FP7 215013) and SMScom (IDEAS 227077).
Reliability Analysis of CB Systems with Multiple Failure Modes
19
References 1. Aviˇzienis, A., Laprie, J., Randell, B., Landwehr, C.: Basic concepts and taxonomy of dependable and secure computing. IEEE JDSC 1(1), 11–33 (2004) 2. Cortellessa, V., Grassi, V.: A modeling approach to analyze the impact of error propagation on reliability of component-based systems. In: Schmidt, H.W., Crnkovi´c, I., Heineman, G.T., Stafford, J.A. (eds.) CBSE 2007. LNCS, vol. 4608, p. 140. Springer, Heidelberg (2007) 3. Hiller, M., Jhumka, A., Suri, N.: Epic: Profiling the propagation and effect of data errors in software. IEEE Transactions Computers 53(5), 512–530 (2004) 4. Ammar, H., Nassar, D., Abdelmoez, W., Shereshevsky, M., Mili, A.: A framework for experimental error propagation analysis of software architecture specifications. In: Proc. of International Symposium on Software Reliability Engineering. IEEE, Los Alamitos (2002) 5. Hissam, S., Moreno, G., Stafford, J., Wallnau, K.: Enabling predictable assembly. Journal of Systems and Software 65(3), 185–198 (2003) 6. Lau, K., Wang, Z.: Software component models. IEEE Transactions Software Engineering 33(10), 709–724 (2007) 7. Cheung, R.C.: A user-oriented software reliability model. IEEE Trans. Softw. Eng. 6(2), 118–125 (1980) 8. Goseva-Popstojanova, K., Trivedi, K.: Architecture based approach to reliability assessment of software systems. Performance Evaluation 45(2-3), 179–204 (2001) 9. Nelson, E.: Estimating software reliability from test data. Microelectronics Reliability 17(1), 67–73 (1978) 10. Horgan, J., Mathur, A.: Software testing and reliability. The Handbook of Software Reliability Engineering, 531–565 (1996) 11. Meeker, W., Escobar, L.: A review of recent research and current issues in accelerated testing. International Statistical Review/Revue Internationale de Statistique 61(1), 147–168 (1993) 12. Podgurski, A., Masri, W., McCleese, Y., Wolff, F.G., Yang, C.: Estimation of software reliability by stratified sampling. ACM Transactions Software Engineering Methodology 8(3), 263–283 (1999) 13. Butler, R.W., Finelli, G.B.: The infeasibility of experimental quantification of lifecritical software reliability. In: SIGSOFT 1991: Proceedings of the conference on Software for Citical Systems, pp. 66–76. ACM, New York (1991) 14. Cinlar, E.: Introduction to stochastic processes, Englewood Cliffs (1975) 15. Katsumi, N.: Fundamentals of linear algebra. McGraw-Hill, New York (1966) 16. Pham, H.: Software reliability and cost models: Perspectives, comparison, and practice. European Journal of Operational Research 149(3), 475–489 (2003) 17. Gokhale, S., Trivedi, K.: Reliability prediction and sensitivity analysis based on software architecture. In: ISSRE, pp. 64–78. IEEE Computer Society, Los Alamitos (2002) 18. Immonen, A., Niemel, E.: Survey of reliability and availability prediction methods from the viewpoint of software architecture. Software and Systems Modeling 7(1), 49–65 (2008) 19. Wang, W., Wu, Y., Chen, M.: An architecture-based software reliability model. In: Pacific Rim International Symposium on Dependable Computing, vol. 0, p. 143. IEEE, Los Alamitos (1999) 20. Reussner, R., Schmidt, H., Poernomo, I.: Reliability prediction for componentbased software architectures. Journal of Systems and Software 66(3), 241–252 (2003)
20
A. Filieri et al.
21. Grassi, V.: Architecture-based dependability prediction for service-oriented computing. In: Proceedings of the WADS Workshop, Citeseer (2004) 22. Abdelmoez, W., Nassar, D., Shereshevsky, M., Gradetsky, N., Gunnalan, R., Ammar, H., Yu, B., Mili, A.: Error propagation in software architectures. In: METRICS 2004, Washington, DC, USA, pp. 384–393. IEEE Computer Society Press, Los Alamitos (2004) 23. Voas, J.: Error propagation analysis for cots systems. Computing and Control Engineering Journal 8(6), 269–272 (1997) 24. Voas, J.: Pie: A dynamic failure-based technique. IEEE Trans. Software Eng. 18(8), 717–727 (1992) 25. Grunske, L., Han, J.: A comparative study into architecture-based safety evaluation methodologies using aadl’s error annex and failure propagation models. In: HASE, pp. 283–292. IEEE Computer Society, Los Alamitos (2008) 26. Cortellessa, V., Singh, H., Cukic, B.: Early reliability assessment of uml based software models. In: Workshop on Software and Performance, pp. 302–309 (2002) 27. Roshandel, R.: Calculating architectural reliability via modeling and analysis. In: ICSE, pp. 69–71. IEEE Computer Society, Los Alamitos (2004) 28. Gokhale, S., Wong, W., Horgan, J., Trivedi, K.: An analytical approach to architecture-based software performance and reliability prediction. Perform. Eval. 58(4) (2004) 29. Blake, J., Reibman, A., Trivedi, K.: Sensitivity analysis of reliability and performability measures for multiprocessor systems. In: SIGMETRICS, pp. 177–186 (1988)
Comparison of Component Frameworks for Real-Time Embedded Systems Petr Hoˇsek1 , Tom´aˇs Pop1 , Tom´aˇs Bureˇs1,2, Petr Hnˇetynka1 , and Michal Malohlava1 1
Department of Software Engineering Faculty of Mathematics and Physics, Charles University, Malostranske namesti 25, Prague 1, 118 00, Czech Republic 2 Institute of Computer Science, Academy of Sciences of the Czech Republic Pod Vodarenskou vezi 2, Prague 8, 182 07, Czech Republic {hosek,pop,bures,hnetynka,malohlava}@dsrg.mff.cuni.cz
Abstract. The usage of components brings significant help in development of real-time embedded systems. There have been a number of component frameworks developed for this purpose and some of them have already become well-established in this area. However, although the component frameworks share basic concepts and the general approach, they substantially differ in the range of supported features and maturity. This makes relatively difficult to select the right component framework and thus poses a significant obstacle in adoption of the component-based development for real-time embedded systems. To provide an overview, we present a survey in this paper, which illustrates distinguishing features of selected modern component-based frameworks for real-time embedded systems. The survey identifies features which are important for building systems from components in this area and compares these frameworks with respect to these features.
1
Introduction
With growing complexity of embedded systems and with the increasing stress on mass production and customization, component-based software engineering is becoming increasingly important in this area. This is testified by growing number of component systems aiming at different application domains of embedded systems (be it AUTOSAR [5] in automotive industry, ROBOCOP [14] in consumer electronics, and emerging standards for component based systems engineering in space industry – as recently demanded by ESA1 SAVOIR initiative). Many years for which the components have been researched have however shown that a proper construction of a component system is not an easy task and 1
http://www.esa.int/
L. Grunske, R. Reussner, and F. Plasil (Eds.): CBSE 2010, LNCS 6092, pp. 21–36, 2010. c Springer-Verlag Berlin Heidelberg 2010
22
P. Hoˇsek et al.
that it is rather difficult to balance features of a component system so that it can provide support and guidance through the whole application development life-cycle. This is due to many factors such as (1) the existence of two distinct but parallel flows of component and system development [11], (2) varying levels of concreteness (e.g. under-specified components such as UML package diagrams for showing application decomposition vs. rigorously defined development components such as with EJB), and (3) varying granularity of components (e.g. using components for modeling low-level signals vs. using components for representing independent subsystems [7]). In this paper, we aim at investigating current component frameworks used in embedded and real-time systems, and at evaluating their suitability in development of such systems. We restrict ourselves to frameworks which employ components not only in high level design but rather throughout the whole development life-cycle. The reason is that preserving the components over the development life-cycle yields better traceability and allows late bindings, even to the extent of run-time reconfigurations and update [34]. There are several papers that attempt to compare, evaluate and classify component systems, e.g. [21,10,24]. However, these works target component systems in general and do not take into account requirements for real-time domain, which have to be satisfied by a chosen component system (otherwise, the system could not be used for development of real-time applications). The structure of the paper is as follows: in Section 2, we set the criteria for evaluating component frameworks in the domain of embedded and real-time systems. Section 3 presents selection of the frameworks chosen for evaluation and describes them. Section 4 evaluates described component frameworks according to the selected criteria and gives recommendations for usage in particular situations. Section 5 concludes the paper.
2
Evaluation Criteria
In order to evaluate the component frameworks, we define a set of criteria here. The criteria cover important concerns of component-based development and also explicitly stress demands of the domain of embedded and real-time systems. For the general component-based development, we use the criteria below (related criteria are grouped together). Their selection was based on features that are generally recognized as important for component-based development [10,8,34]. In addition to those criteria, we also implicitly assume the execution support, which means that the component framework allows execution of the component application – either by directly providing the execution environment, by offering synthesis of executable code or by allowing deployment to another existing component framework. The criteria are:
Comparison of Component Frameworks for Real-Time Embedded Systems
23
– existence of a well-defined component model with support for advanced features such as hierarchical nesting, multiple communication styles and behavior and/or resource usage modeling (C1) – existence of development tools or a development environment (e.g. graphical IDE) (C2a) – existence of a development methodology or at least guidelines for development process (C2b) – support for distributed component applications (C3a) – support for dynamic reconfiguration and/or update at run-time (C3b) – existence of a documentation on a sufficient level (C4a) – status of a development tools and component execution support (i.e. whether they are actively developed or abandoned) (C4b) In order to take into consideration also the requirements coming from the domain of embedded and real-time system, we additionally define the following criteria. They are again based on general requirements impose on embedded and real-time system [9,31]. The criteria are: – support for coupling with hardware (C5a) – small or configurable memory footprint of the run-time environment or the synthesized glue code, which are necessary to components’ execution and interaction (C5b) – support for modeling real-time attributes (e.g. period, budget) and scheduling analysis (C6a) – support for periodic and aperiodic tasks (C6b) – support for real-time scheduling of components at run-time (C6c)
3
Component Frameworks Overview
In this section, we briefly describe component frameworks targeting development of real-time applications for embedded devices. As we stated in Section 1, we have restricted ourselves to frameworks that support execution. This restriction leaves out purely modeling approaches (e.g. OMG MARTE). We have also deliberately omitted component frameworks for enterprise systems (e.g. CCM, EJB, COM, DCOM) as well as for user interfaces (e.g. JavaBeans) and web applications (e.g. JSF). These frameworks are not suitable for development of real-time embedded systems. Additionally, we do not cover modeling, analysis, and simulation tools like Mathlab/Simulink, Scade. These could be considered under some circumstances also as component models but their concept of a component is basically on the level of logical or mathematical operator, which makes them incomparable with classical component models that rely on components with functional interfaces. Based on these criteria, we have chosen the following frameworks: PECOS, MyCCM-HI, Progress, AUTOSAR, Pin, Koala, ROBOCOP, THINK, SOFA HI and BlueArX.
24
3.1
P. Hoˇsek et al.
PECOS
PECOS (Pervasive Component Systems) [26] is a project targeting the domain of field devices, i.e. reactive real-time embedded systems that are typically nondistributed systems reading periodically sensors, analyzing results, and reacting by influencing actuators [13]. PECOS uses a component model, whose syntax is described by a meta-model and its execution by Petri nets. The meta-model enables specifying hierarchical components, component types, and property bundles that hold extra-functional properties of components. Composition checking is implemented by generating Prolog facts during composition process and then by verifying appropriate semantic rules against a set of Prolog queries. Timing schedules were planned to be generated from timing Petri nets. PECOS does not seem to aim at providing development methodology or advanced development tools like IDE or shared component repository. Just composition tools are available for download, run-time environment and composition compilers are not available. Project was actively developed in the past; now, according to its web pages [29], it seems to be dead. 3.2
MyCCM-HI
MyCCM-HI [25] is based on OMG Lightweight CCM [27]. MyCCM-HI offers composite components and it supports the C and Ada95 languages. The main idea behind MyCCM-HI is transformation of application model based on component approach (described in language called COAL) to lowerlevel AADL Language [1]. Then, Ocarina AADL compiler [36] can be used to produce executable files or Petri net model of the application. Ocarina libraries are also used to perform schedulability analysis [19] using the Chedar tool [32]. Distributed inter-component communication is realized by Poly-ORB-HI – middleware specialized for high integrity systems. Mode based reconfiguration of inter-component connections is also supported. Similarly to PECOS, neither MyCCM-HI seems to be aiming at producing support tools or methodology such as development IDE, direct support for repository to enable or simplify component reuse, and systematic development methodology. On the other hand, command line tools are in mature development stage. These tools and runnable examples are freely available. 3.3
PROGRESS/ProCom
Progress is an ambitious project aiming at providing theory and tools for a cost-efficient engineering and re-engineering of distributed component based software, mostly focused on embedded systems. Because Progress is primarily intended to be used in the automotive, telecommunication and automation industry, strong emphasis is given to time analysis and reliability of modeled systems.
Comparison of Component Frameworks for Real-Time Embedded Systems
25
The Progress component model (called ProCom [7]), which is based on SaveCCM [16] and Rubus [15], distinguishes two levels of granularity – ProSys and ProSave. ProSave, the lower layer, operates with low-level, passive, hierarchically structured components. Computation on this level is based on the pipe-and-filter paradigm, the functionality of the ProSave component is described as a set of services. A component can have several independent (and possibly concurrently running) services. The communication between components is realized by data ports and triggering ports. Each service contains one input port group and several output port groups. Passivity means that components cannot start any activity themselves. The services can be started only by triggering the input port. ProSys, the upper layer, describes a set of concurrent subsystems. These subsystems can run potentially on several computation hardware units, called physical nodes. ProSys subsystem is composed of a set of concurrent functionality that can be either event driven (sporadic) or periodic. The only way for ProSys subsystems to communicate with each other is sending asynchronous messages via channels. The channels are strongly typed and support multiple senders and receivers. A ProSys component may be modeled as an assembly of ProSave components. Progress is actively developed. Thus, related analysis methods, deployment tools, etc., are not yet implemented. At present, there is only a prototype of the Eclipse-based IDE and documentation of the component model. 3.4
AUTOSAR
AUTOSAR (Automotive Open System Architecture) [4] is an open industrial standard aiming at precise architecture description enabling many different manufacturers from the automotive industry to integrate their products together. AUTOSAR distinguishes atomic software components, representing pieces of functionality, and compositions, representing logical interconnection of components. An atomic component is limited to one electronic control unit (ECU). The standard supports two communication models – Client-Server (both blocking and non-blocking communication semantic are supported) and SenderReceiver. In the former case, the client receives a reply from the server, in the latter case, there is no replay assumed and more receivers may receive the message. AUTOSAR provides basic software support to components (including run-time environment, micro-controller abstraction or support for component communication). Support for real-time properties is not clearly specified. Nevertheless, taking into account that AUTOSAR requires a real-time operating system, it is reasonable to assume that atomic components can use real-time primitives of the operating system. AUTOSAR defines development process methodology; it describes development activities and their mutual dependencies.
26
P. Hoˇsek et al.
3.5
Pin
Pin Component technology [18] is an initiative with the goal to provide freely distributable technology providing a basic set of features to design and implement predictable real-time software for embedded devices with support for the UML state-charts semantic. Components in the Pin model are architectural units specifying stimulusresponse behavior by a set of input ports (sink pins), output ports (source pins), and reactions to sink pins (this is very close to ProSave layer found in Progress). Each Pin component consists of a container and custom code and is delivered as a dynamically linkable library with well specified interactions with environment and other components. Custom code logically consists of sink port message handlers and timeout handlers; for each reaction a single thread is created. In the current version of Pin, architectural topology is fixed at run-time, no dynamic reconfigurations are allowed, and system cannot run in a distributed environment. Real-time features are provided via a support of an underlying external commercial environment. Pin supports synchronous and asynchronous connectors, while message size and message queue lengths are fixed. Components are defined in CCL (Construction and Composition Language), the functionality is specified in the host language (C). Pin is currently ported to Windows NT and Windows CE operating systems. Pin model is not connected to any development methodology and supporting tools such as IDE or repository, but it is implemented and with a set of basic development tools available for download. 3.6
Koala
Koala [28] is a component model for embedded devices developed by Philips2 . Primary goals of Koala are to manage increasing complexity of software used mostly in consumer electronics and to manage its diversity. The component model itself is inspired by COM and Darwin component models. Its basic elements are components defining set of provided and required interfaces. There is also a concept of diversity interfaces, which are used to capture variation points of components. The component model supports hierarchical components (called compound components). The components are implemented in the C language. Koala compiler is used to generate C header files responsible for connecting components together. The component interfaces are statically connected to each other at design time. Nevertheless, the model offers different ways to handle diversity such as switches which can be used to handle structural diversity at design time as well as partially at runtime. The Koala component model targets simple embedded devices and therefore it is strongly focused on optimization. This, however, makes it really difficult to perform the analysis of the run-time properties. Koala uses a global web-based repository to store interfaces and components. 2
http://www.philips.com/
Comparison of Component Frameworks for Real-Time Embedded Systems
3.7
27
ROBOCOP
The Robust Component Model for Consumer Electronic Products (ROBOCOP) [14] is a component model developed as an ITEA3 project, which defines open, component-based architecture for the middleware layer in high-volume consumer electronic products. The ROBOCOP architecture consists of different frameworks. The development framework defines a number of aspects relevant to the development and trading of components consisting of a number of different elements such as the stakeholder roles and their relations, the component model, and tooling. The run-time framework defines the execution environment for components. The download framework enables dynamic upgrade and extension by allowing controlled downloading of components from a repository to a device, while the resource management framework provides mechanism for components to negotiate their resource needs. The resource management framework can be then used to specify for example timing properties if the implementation supports them. Beyond the frameworks described, the ROBOCOP also defines components. A component in ROBOCOP is defined as a collection of models and relations between these models. This allows different concepts to be employed such as trading, composition and execution-time properties specification. Functionality of components is encapsulated in services, each of which defines a set of provided and required interfaces and also third party bindings. The interfaces are considered as first-class entities. At run-time, services are instantiated dynamically; these service instances are an analogy of objects. The interfaces, described using RIDL language, are represented at run-time by interface instances. They also supports interface inheritance. One object can be accessed through multiple interface instances by multiple clients. The programming model strictly follows the Microsoft COM model; binary mappings to different programming languages can be defined. At run-time, the application is composed of executable components and Robocop Run-time Environment, which takes care of component creation. 3.8
THINK
THINK [12] is a C-implementation of the Fractal [6] component model targeted at embedded systems. The original purpose of THINK was to simplify the development of kernels for embedded devices, but gradually it developed into a full-featured component system generally usable for embedded software development. Because THINK is a Fractal implementation, each component provides a standard API for introspection, configuration and instantiation of component. These various aspects are captured in different controllers.
3
http://www.itea2.org/
28
P. Hoˇsek et al.
Component functional code is written in the C language extended with annotations, called nuptC. Using THINK Compiler, different transformations and optimizations can be applied to the code. Non-functional properties of components are managed using a extension of the THINK ADL, which allows specifying properties for any architectural entity. There are several predefined properties and new properties may be added using THINK Compiler plug-in mechanism. The properties can be used to define additional views on component model, such as behavioral and reconfiguration view as described in [3]. Due to its origins, THINK offers Kortex library, which is a component library containing generic as well as hardware specific components that can be used to build and access operating systems services. Part of the THINK project is also an Eclipse-based IDE called thinkClipse. The IDE offers basic support for development of components using THINK component system. THINK is now being adopted in the recently started project MIND [23], which attempts to build a new framework for industrial use based on THINK. 3.9
SOFA HI
SOFA HI [30] is an extension of SOFA 2 component model [8] targeted at highintegrity real-time embedded systems. The goal of SOFA HI is to bring the knowledge of component systems gained from SOFA and SOFA 2 development into the real-time environment to speed up the development and lower the costs of high-integrity systems. SOFA 2 is an advanced distributed component system based on hierarchically composed components. Moreover, SOFA 2 provides complete support for all the stages of application development and deployment life-cycle. The component model itself is defined by means of its meta-model. The artifacts of the application component model are stored in a repository, which manages their versioning and provides access to development tools and run-time environment. SOFA 2 components are types specifying provided / required interfaces and behavior. Each component internally contains microcomponents which define control a part of component (in a similar way to Fractal / THINK). As a profile of SOFA 2 targeted at real-time embedded systems, SOFA HI imposes various restrictions on the component model in order to make it more predictable and lightweight. The component meta-model also supports specification of extra-functional properties such as component timing properties. As opposed to SOFA 2, SOFA HI disallows generation of connectors and controllers at run-time and also dynamic architecture reconfigurations are restricted to mode switches at run-time only. SOFA HI run-time as well as SOFA HI primitive components are implemented in the C programming language with the help of existing SOFA 2 tools and infrastructure. To ensure sufficient independence of the component implementation, SOFA HI defines an abstraction layer on top of the underlying OS and HW.
Comparison of Component Frameworks for Real-Time Embedded Systems
29
A wide range of existing development tools for SOFA 2 component model can be also used for developing SOFA HI application. They include Cushion as a command line development and management tool, SOFA 2 IDE for application modeling and development based on top of Eclipse as well as SOFA 2 MConsole for application deployment. There are also tools allowing formal analysis and verification of component behavior. 3.10
BlueArX
BlueArX[20] is a component system developed and used by Bosch4 intended for use in automotive domain especially in embedded devices. The BlueArX focuses on the design time component model to support constrained domains considering various non-functional requirements while providing different views of a developed system. The static view defines two types of components, an atomic component, which has an implementation, and a structural component. While atomic components represents leafs in the software architecture tree, the structural components represents nodes. Structural component may be composed of several atomic and/or structural components. A component has interfaces, which are divided into two types – import and export interfaces – where import interface are required and export interfaces are provided by the component. A structural component can import or export a subset of interfaces from each atomic and structural component it is composed of. Connection between interfaces is implicit based on interface name. Communication between components is done using special type of variable called message where component specifies its message access properties in its interface description. The dynamic view consists of component scheduling specification, which contains mapping of services to periodic or event-triggered tasks and the order of services inside these tasks. This information is used to generate a system schedule which is therefore used by the operating system called ERCOS. ERCOS is a specially designed operating system for automotive applications supporting cooperative and preemptive tasks. The BlueArX component model also defines modes, which can be used to define either different scheduling or different control strategies of the real-time system. The modes are also referenced by the analytic interface, which allows to specify non-functional properties and semantic context information associated with components. The concept of modes is important because it allows to express real-time properties of processes much more precisely. BlueArX also defines a simple development process composed of different steps and roles associated together with different activities of application development life-cycle. The BlueArX component system also provides various development tools such as a tool, which can automatically generate annotations for AbsInt aiT[2] which 4
http://www.bosch.com/
30
P. Hoˇsek et al.
allows to estimate WCET of a component, XGen for semi-automatic extraction of mode candidates based on heuristic, and Artus-eMercury IDE built on top of Eclipse.
4
Evaluation
In this section, we evaluate the frameworks briefly described in the previous section. For better readability and comprehensibility, we divide the evaluation into several parts; each of them based on a different criterion defined in Section 2. 4.1
Component Models and Their Features
In this section we evaluate the component models used in the frameworks (i.e. the criterion C1). We focus on model features which are important from componentbased development in general but not directly related to real-time and/or embedded systems. Most of the considered frameworks offer a hierarchical component model, at least at design time. These are MyCCM-HI, PECOS, PROGRESS (on both ProSave and ProSys layer), AUTOSAR, Koala, and ROBOCOP. The Pin frameworks allows only a flat architectures without any hierarchy. Conversely THINK, SOFA HI, and BlueArX support hierarchical components from design time to run-time. Considering other advanced features of component models, SOFA HI offers first-class connectors with multiple communication styles. Koala and ROBOCOP also support the concept of connectors but they are used at design-time for modeling variability in component design. Most of the considered models provide some kind of formal specification or execution model of component behavior and its validation – some of them have full support for validation (Petri nets are used for MyCCM-HI and PECOS, behavioral protocols for SOFA HI), other have well defined execution model (UML state-charts in case of Pin, exact description in manual in case of ProCom). Fractal execution model has been described using Alloy specification language [22]; this technique could be also used for THINK as it is a Fractal implementation. The summary of the frameworks with regard to the criterion C1 is presented in Table 1. 4.2
Development Support
In this section, we focus on existence of development tools and development methodologies (or at least development guidelines) for the selected frameworks (i.e. the criteria C2a and C2b). To some extent, development tools exist for all of the selected frameworks. However, it is hard to evaluate them for frameworks such as ROBOCOP and BlueArX since they are not publicly available. AUTOSAR is more of a standard, so no tool support is required. In case of the remaining frameworks, tools are available, nevertheless, some of them are obsolete (Koala, Pin), incomplete
Comparison of Component Frameworks for Real-Time Embedded Systems
31
Table 1. Evaluation of component models
PECOS MyCCM-HI PROGRESS AUTOSAR Pin Koala ROBOCOP THINK SOFA HI BlueArX
Hier. comp.
Connectors
Formal. behav./Ex. model
design-time design-time design-time fully no design-time design-time fully fully fully
yes no no no no yes yes no yes no
yes yes yes no yes yes yes yes yes yes
(PECOS) or under development (PROGRESS, SOFA HI). Ready-to-use tools are only available for MyCCM-HI and THINK. Both of them transform the component descriptions into models of other technologies and then reuse the tool of these technologies. For example, MyCCM-HI relies on AADL and its compiler to create executable files. This however leads to loosing the component structure during deployment, which negatively influences in traceability, verification, and potential run-time updates. A development methodology is available for AUTOSAR and BlueArX. The results are summarized in Table 2. Table 2. Development support
PECOS MyCCM-HI PROGRESS AUTOSAR Pin Koala ROBOCOP THINK SOFA HI BlueArX
4.3
Devel. tools
Devel. methodology
incomplete basic under development no basic basic no yes under development yes
no no no yes no no no no no yes
Support of Distributed and Dynamic Applications
Evaluation of support of distributed (the criterion C3a) and dynamic (the criterion C3b) applications is presented in this section. Distributed applications are supported by MyCCM-HI, PROGRESS, AUTOSAR, and SOFA HI. The remaining frameworks support only non-distributed systems.
32
P. Hoˇsek et al.
By support of dynamic applications, we mean the ability to develop an application which can change its architecture at run-time, i.e. to add/remove components and/or bindings among them. The frameworks with such an ability are MyCCM-HI, ROBOCOP, SOFA HI, BlueArX and partially Koala. ROBOCOP allows for partial dynamism by offering dynamic upgrade and download of a component to a device. MyCCM-HI, SOFA HI, and BlueArX support dynamic applications via so called modes [17], i.e. an application can have several possible architectures and they can be switched among each other at some well-defined points. Table 3 provides the summary of the section. Table 3. Support of distributed and dynamic applications Distributed apps.
Dynamic apps.
no yes yes yes no no yes no yes no
no yes no no no partial partial no yes yes
PECOS MyCCM-HI PROGRESS AUTOSAR Pin Koala ROBOCOP THINK SOFA HI BlueArX
4.4
Status of the Frameworks
The availability of documentation (the criterion C4a) and the overall status (the criterion C4b) are other important aspects. At least partial documentation exists for all evaluated frameworks. Nevertheless, in many cases such documentation consists only of several research papers (e.g. Koala, PROGRESS, SOFA HI, PECOS). The specification and detailed documentation for the industrial systems such as ROBOCOP or BlueArX is not publicly available. AUTOSAR is better in this respect – its documentation is publicly available under the AUTOSAR partnership license. With respect to the status of the frameworks, some of them seem to be abandoned or not actively maintained. These are PECOS, Pin, Koala, and ROBOCOP, as there has been no activity for quite a long time (results of ROBOCOP have been used in the Space4U [33] and Trust4All [35] projects). PROGRESS and SOFA HI are currently under heavy development, while MyCCM-HI and THINK are rather stable, maintained, and further developed. The AUTOSAR and BlueArX are used in the industry, however, again since they are not publicly available, any other conclusions are not possible. Table 4 summarizes the section.
Comparison of Component Frameworks for Real-Time Embedded Systems
33
Table 4. Status of the frameworks Status PECOS MyCCM-HI PROGRESS AUTOSAR Pin Koala ROBOCOP THINK SOFA HI BlueArX
4.5
abandoned ready-to-be-used under development ready-to-be-used not actively developed abandoned abandoned ready-to-be-used under development ready-to-be-used
Coupling with Hardware and Suitability for Embedded Systems
All of the considered frameworks are intended for embedded systems and thus provide support for low-level coupling with hardware, managing low memory footprint and other necessary features for embedded systems (i.e. criteria C5a and C5b). Coupling with hardware is typically provided by run-time environment, which provides an abstraction over a supported set of hardware platforms. For example the implementation of the Pin framework relies on OS services of Windows NT and Windows CE; SOFA-HI, PECOS and Progress require other particular real-time operating systems. Similarly, AUTOSAR requires microcontroller abstraction as defined in its specification. 4.6
Support for Real-Time
Koala, ROBOCOP, and THINK do not offer explicit support for real-time applications. For AUTOSAR, the support of real-time properties is not clearly specified, however, since it is used for real-time applications in industry, it can be assumed Table 5. Support of real-time applications
PECOS MyCCM-HI PROGRESS AUTOSAR Pin Koala ROBOCOP THINK SOFA HI BlueArX
Attr. and analysis
Support for periodic. aperiodic tasks
Schedulability analysis
yes yes yes yes yes no no no yes yes
both both both not specified aperiodic no no no both both
was planned yes is planned yes external no no no is planned yes
34
P. Hoˇsek et al.
that it supports them. The rest of the considered frameworks primarily target realtime systems and satisfy all three criteria (C6a, C6b, and C6c). The summary is provided in Table 5. 4.7
Summary
As apparent from the sections above, there is no clear winner framework suitable for development of embedded and realtime systems. However, with some limitations, the most suitable frameworks can be signed out. For the automotive domain, the clear winners are AUTOSAR and BlueArX. The downside is that they are not publicly available, however, for the intended domain it is not an issue. Additionally, AUTOSAR can be considered as more perspective since it is designed and developed by a consortium of companies while BlueArX seems to be backed by a single company. For non-automotive domain and from the short time view, the options are MyCCM-HI or THINK as both of them are publicly available and ready-to-beused, also with some tool support. The downside of THINK is that it does not support real-time properties. For non-automotive domain, from the short-term point of view, the options are MyCCM-HI or THINK as both of them are publicly available and ready-tobe used, also with some tool support. The downside of THINK is that it does not support real-time properties. From the long-term perspective, most promising technologies seems to be the SOFA-HI and PROGRESS frameworks as they target a clear model-driven approach of design and development. Moreover, SOFA-HI builds on existing SOFA 2 development environment, which comprises an extensive tool-set including graphical Eclipse-based IDE, graphical deployment, run-time console, shared component repository, and various analysis tools.
5
Conclusion
In this paper, we have overviewed a number of state-of-the-art component frameworks suitable for building real-time embedded systems. Our aim was to provide guidance for choosing the right framework, as they significantly differ in offered features and maturity. To provide common criteria, we have consulted existing literature and identified the features which are important for building real-time embedded systems using components. We have evaluated the discussed component frameworks and compared them with respect to the defined criteria. The results of the evaluation show that there is no single universal “winner”, therefore we have formulated recommendations based on the intended use of a component framework. These recommendations provide a guide in selection of a suitable component technology based on the the specific requirements of each application. The presented results also open space for further research in this area of software research.
Comparison of Component Frameworks for Real-Time Embedded Systems
35
Acknowledgments This work was partially supported by the Grant Agency of the Czech Republic project 201/08/0266. We also thank Lubomir Bulej for his valuable comments and suggestions.
References 1. AADL predictable model-based engineering, http://www.aadl.info/aadl/currentsite/ 2. AbsInt aiT WCET Analyzers, http://www.absint.de/ait/ 3. Anne, M., He, R., Jarboui, T., Lacoste, M., Lobry, O., Lorant, G., Louvel, M., Navas, J., Olive, V., Polakovic, J., Poulhis, M., Pulou, J., Seyvoz, S., Tous, J., Watteyne, T.: Think: View-Based Support of Non-functional Properties in Embedded Systems. In: 2009 International Conference on Embedded Software and Systems, pp. 147–156 (May 2009) 4. Autosar website, http://www.autosar.org/ 5. AUTOSAR GbR: Autosar-technical overview. Technical Report, AUTOSAR GbR (2005) http://www.autosar.org/index.php?p=3&up=1&uup=0 6. Bruneton, E., Coupaye, T., Stefani, J.B.: The Fractal Component Model, http://fractal.ow2.org/specification/ 7. Bures, T., Carlson, J., Crnkovic, I., Sentilles, S., Vulgarakis, A.: ProCom - the Progress Component Model Reference Manual, version 1.0. Technical Report, M¨ alardalen University (June 2008), http://www.mrtc.mdh.se/index.php?choice=publications&id=1508 8. Bures, T., Hnetynka, P., Plasil, F.: SOFA 2.0: Balancing Advanced Features in a Hierarchical Component Model. In: Proceedings of SERA 2006, Seattle, USA, pp. 40–48 (August 2006) 9. Buttazo, G.C.: Hard Real-time Computing Systems Predictable Scheduling Algorithms and Applications, 2nd edn. Springer, Heidelberg (2005) 10. Crnkovic., Chaudron, M., Sentilles, S., Vulgarakis, A.: A classification framework for component models. In: Proceedings of the 7th Conference on Software Engineering and Practice in Sweden (October 2007), http://www.mrtc.mdh.se/index.php?choice=publications&id=1374 11. Crnkovic, I., Larsson, M.: Building Reliable Component-based Software Systems. Artech House, INC., Norwood (2002) 12. Fassino, J.-P., Stefani, J.-B., Lawall, J., Muller, G.: Think: A Software Framework for Component-based Operating System Kernels. In: Proceedings of the 2002 USENIX Annual Technical Conference, Monterey, California, USA (June 2002) 13. Genler, T., Nierstrasz, O., Informatik, F.: Components for embedded software the pecos approach (2002) 14. Maaskant, H.: A Robust Component Model For Consumer Electronic Products. In: Dynamic and Robust Streaming in and between Connected Consumer Electronic Devices, vol. 3, pp. 167–192. Springer, Netherlands (2005) 15. Hanninen, K., Maki-Turja, J., Nolin, M., Lindberg, M., Lundback, J., Lundback, K.L.: The rubus component model for resource constrained real-time systems (June 2008) 16. Hansson, H., AAkerholm, M., Crnkovic, I., Torngren, M.: SaveCCM - a component model for safety-critical real-time systems, August 2004, pp. 627–635 (2004)
36
P. Hoˇsek et al.
17. Hirsch, D., Kramer, J., Magee, J., Uchitel, S.: Modes for software architectures. In: 3rd European Workshop on Software Architecture (2006) http://publicaciones.dc.uba.ar/Publications/2006/HKMU06 18. Hissam, S., Ivers, J., Plakosh, D., Wallnau, K.C.: Tech. rep 19. Hugues, J., Zalila, B., Pautet, L., Kordon, F.: Rapid Prototyping of Distributed Real-Time Embedded Systems Using the AADL and Ocarina. In: IEEE International Workshop on Rapid System Prototyping, pp. 106–112 (2007) 20. Kim, J.E., Rogalla, O., Kramer, S., Hamann, A.: Extracting, specifying and predicting software system properties in component based real-time embedded software development. In: 31st International Conference Software Engineering Companion Volume, 2009. ICSE-Companion 2009, pp. 28–38 (May 2009) 21. Lau, K.K., Wang, Z.: Software component models. IEEE Trans. Softw. Eng. 33(10), 709–724 (2007) 22. Merle, P., Stefani, J.B.: A formal specification of the Fractal component model in Alloy. Technical Report (November (2008), http://hal.inria.fr/inria-00338987/fr/ 23. The MIND project, http://www.minalogic.com/en/posters/Mind_eng-web.pdf 24. Moller, A., Akerholm, M., Fredriksson, J., Nolin, M.: Evaluation of component technologies with respect to industrial requirements. In: EUROMICRO 2004: Proceedings of the 30th EUROMICRO Conference, Washington, DC, USA, pp. 56–63. IEEE Computer Society Press, Los Alamitos (2004) 25. MyCCM High Integrity, http://sourceforge.net/apps/trac/myccm-hi/wiki 26. Nierstrasz, O., Ar´evalo, G., Ducasse, S., Wuyts, R., Black, A.P., M¨ uller, P.O., Zeidler, C., Genssler, T., Born, R.: A component model for field devices. In: Bishop, J.M. (ed.) CD 2002. LNCS, vol. 2370, pp. 200–209. Springer, Heidelberg (2002) 27. OMG Group: Corba component model specification. Tech. rep., OMG Group (2006) 28. Ommering, R., Linden, F., Kramer, J., Magee, J.: The Koala Component Model for Consumer Electronics Software. Computer 33, 78–85 (2000) 29. PECOS Website, http://scg.unibe.ch/archive/pecos/ 30. Prochazka, M., Ward, R., Tuma, P., Hnetynka, P., Adamek, J.: A ComponentOriented Framework for Spacecraft On-Board Software. In: Proceedings of DASIA 2008, DAta Systems In Aerospace, Palma de Mallorca (May 2009) 31. Siewert, S.: Real-Time Embedded Components and Systems. Cengage Learning (June 2006) 32. Singhoff, F., Legrand, J., Nana, L., Marc´e, L.: Cheddar: a flexible real time scheduling framework. Ada Lett. XXIV(4), 1–8 (2004) 33. Space4U, http://www.hitech-projects.com/euprojects/space4u/ 34. Szyperski, C.: Component Software: Beyond Object-Oriented Programming, 2nd edn., (Hardcover) Addison-Wesley Professional, Reading (2002) 35. Trust4All, http://www.hitech-projects.com/euprojects/trust4all/ 36. Vergnaud, T., Zalila, B.: Ocarina: a Compiler for the AADL. Technical Report, Tlkom Paris (2006) http://ocarina.enst.fr/
A Three-Tier Approach for Composition of Real-Time Embedded Software Stacks Fr´ed´eric Loiret1 , Lionel Seinturier1 , Laurence Duchien1 , and David Servat2 1
2
INRIA-Lille, Nord Europe, Project ADAM Univ. Lille 1 - LIFL CNRS UMR 8022, France {frederic.loiret,lionel.seinturier,laurence.duchien}@inria.fr CEA, LIST, Laboratory of Model Driven Engineering for Embedded Systems, Point Courrier 94, Gif-sur-Yvette, 91191, France
[email protected] Abstract. Many component models and frameworks have been proposed to abstract and capture concerns from Real-Time and Embedded application domains, based on high-level component-based approaches. However, these approaches tend to propose their own fixed-set abstractions and ad-hoc runtime platforms, whereas the current trend emphasizes more flexible solutions, as embedded systems must constantly integrate new functionalities, while preserving performance. In this paper, we present a two-fold contribution addressing this statement. First, we propose to express these concerns in a decoupled way from the commonly accepted structural abstractions inherent to CBSE, and provide a framework to implement them in open and extensible runtime containers. Second, we propose a three-tier approach to composition where application, containers and the underlying operating system are designed using components. Supporting a homogeneous design space allows applying optimization techniques at these three abstraction layers showing that our approach does not impact on performance. In this paper, we focus our evaluation on concerns specific to the field of real-time audio and music applications.
1
Introduction
Component-Based Software Engineering is nowadays applied for a wide range of application domains, from IT systems using mainstream component technologies such as EJB or CCM to real-time and embedded (RTE) systems. Beyond the well-established advantages in terms of packaging and composability of independently-implemented software modules, CBSE promotes flexible design approaches, relying on separation of concerns embodied by the software’s architecture. Moreover, the capability to specialize the architecture with relevant abstractions and non-functional concerns of an application domain, conduct the definition of Domain-Specific Component Frameworks (DSCF). Thereby, DSCF offers a domain-specific component model and a tool-support allowing these nonfunctional concerns to be deployed in the runtime platform composed of a set of custom made containers. It is especially the case for RTE Component Frameworks relieving the developer from dealing with redundant and error-prone tasks L. Grunske, R. Reussner, and F. Plasil (Eds.): CBSE 2010, LNCS 6092, pp. 37–54, 2010. c Springer-Verlag Berlin Heidelberg 2010
38
F. Loiret et al.
such as creating threads, managing synchronization or activation of the components with temporal constraints, or performing inter-task communications (ITC). Problem Statement. Many RTE Component Frameworks have been proposed to address these non-functional concerns [6,9,24]. However, these propositions tend to provide their own abstractions, fixed set of execution and communication models, and their own ad-hoc runtime platforms. We believe that proposing more flexible solutions is a key issue to consider in order to improve reuse and integration of legacy solutions. Indeed, i) components can be independently deployed in heterogeneous execution contexts depending on embedded or temporal requirements. ii) Runtime platforms must be adapted according to new non-functional concerns as embedded systems must constantly integrate new functionalities. However, RTE constraints are tightly dependent on the runtime platform and on the underlying operating system since these layers must not induce a significant overhead concerning critical metrics of the domain, such as memory footprint, real-time responsiveness and execution time. The respect of these constraints is thus a prerequisite for introducing flexibility in embedded software stacks. Contributions. To address these challenges, the contribution of this paper relies on two parts which are integrated into a full-fledged framework. First, we propose the use of a generic component-based framework extensible towards various domain-specific concerns [13]. These concerns are specified by the developer in a flexible way via the use of annotations specializing the architectural artefacts (components, interfaces, bindings), according to the execution contexts required by the application. Our framework relies on an approach for generation and composition of component-based containers implementing the runtime platform which fits the application’s requirements. Second, we exploit a component-based implementation of a real-time operating system presented in [14] providing the low-level services required by the containers. As a result, we present in this paper a three-tier approach where an embedded software stack made of componentbased application, containers and operating system is composed using a homogeneous component model. A homogeneous support of the component paradigm is the key point of our contribution to support flexibility into the software stack at these three abstraction levels. Moreover, i) by composition, only the services strictly required by the whole system are deployed in the final executable to fulfill the embedded constraints. ii) Our approach relies on optimization techniques which are applied uniformly at these three abstraction layers reducing at a negligible level its impact on the performances. In this paper, we apply this approach to the design of real-time audio applications. Outline. The paper is structured as follows: The two building blocks on which our contribution relies are presented in Section 2, and a general overview of our approach is outlined in Section 3. Section 4 presents our contribution to designing component-based applications dedicated to real-time audio concerns. The composition process involved to implement these concerns are detailed in Section 5 and evaluated in Section 6. Finally, we discuss related work in Section 7 before concluding and drawing future directions of our research in Section 8.
A Three-Tier Approach for Composition of RTE Software Stacks
2
39
Background
In this section, we present two building blocks on which the contributions of this paper rely. 2.1
Hulotte Component-Based Framework
Hulotte [13] defines a component metamodel introduced in Figure 1. This metamodel is based on general CBSE principles [4], and is inspired by the reflective Fractal component model [3]. In particular, Hulotte identifies as core architectural artefacts the concepts annotatedBy Architectural of Component (either Primitive or Artefact Domain-Specific * Annotation + name: String Composite), Attribute, Interface, Attribute and Binding. The behavior of a * primitive component is implemented * Interface Component by the underlying programming language supported by our framework * subComponent source destination superComponent (the C language in the context of this Primitive Composite Binding Content Component Component paper) and is reified by the Content 1 * artefact. An architecture is then specified as a set of interconnected comFig. 1. The Hulotte Metamodel ponents (at an arbitrary level of encapsulation by using the composite design pattern) via oriented relationships between Interfaces. We distinguish two roles involved in the Hulotte development process: the application developer and the platform developer. The application developer is responsible for the development of applicative components and the specification of their domain-specific requirements. She/he uses the Hulotte metamodel concepts, depicted in Figure 1, to design the component-based application, which is afterwards annotated by Domain-Specific Annotations. These annotations mark the Hulotte Architectural Artefacts like Java 5 annotations mark the Abstract Syntax Tree (AST) of a Java program. Hulotte annotations isolate and specify the concerns relevant to a targeted application domain, so-called domain-specific concerns. Within our approach, it should be noticed that components are used as pure business units, and a component-based architecture then implements the whole business logic of the application. Therefore, annotations are used to specify the domain-specific semantics over the architecture. For instance, in order to address the multitask applications domain, an annotation can be used on a component to qualify under which execution model its business interfaces should be invoked, e.g. periodically or sporadically. As a second example, on a composite component, an annotation can qualify the boundary of a memory scope in which its subcomponents will be allocated. Finally, an annotation can also be used on a binding to specialize the communication models and protocols (e.g., asynchronous, shared memory, CORBA, SOAP) between the components it binds.
40
F. Loiret et al.
The role of the platform developer is to design the runtime platform implementing the domain-specific requirements specified by the application developer. Hulotte relies on a reflective architecture where each applicative component is hosted by a container, which is itself implemented as a component-based architecture. Throughout this paper, we will refer to “platform components” encapsulated within the containers. Therefore, a container is also implemented using the architectural concepts presented in Figure 1. 2.2
Real-Time Operating System Componentization
In [14], we have conducted a component-based reengineering of µC/OS-II [2], a preemptive, real-time multitasking kernel for microprocessors and microcontrollers. It is implemented in ANSI C and certified by the FAA1 software intended to be deployed in avionics equipment. It has been massively used in many embedded and safety critical systems products worldwide. µC/OS provides the basic services of a Real-Time Operating System (RTOS): Task and Time management, Inter-Process Communications (mailbox, queues, semaphore, mutex), and Interrupts management. The execution time for most of these services is both constant and deterministic. µC/OS is implemented as a monolithic kernel, i.e. it is built from a number of functions that share common global variables and data types (such as task control block, event control block, etc). The reengineering presented in [14] consists of a library of ready-to-use RTOS configurations, implemented by a set of composite components, providing their services to the application. We showed that overheads involved by our component-based design in term of performance were negligible compared to the original monolithic implementation of the kernel.
3
General Overview of the Approach and Case Study
This section presents a general overview of our three-tier approach for composition of a real-time embedded software stack, presents the application domain and the case study on which it is applied in this paper. 3.1
General Overview
The software stack is sketched in Figure 2. At the higher abstraction level, the application developer specifies the architecture of the application as a set of components annotated by domain-specific annotations. In the context of this paper, annotations are used for designing real-time audio applications and will be presented in Section 4. As a first stage of composition (Fig. 2(1)), these components are composed within component-based containers implementing the semantics of the domain-specific annotations in a transparent manner for the application developer. The implementation of containers by the platform developer and the composition rules between applicative and platform components 1
Federal Aviation Administration.
A Three-Tier Approach for Composition of RTE Software Stacks
41
Applicative Component Conforms to
Annotates
Applicative Component
Applicative Component
Application
Component-Based Application
1
Domain-Specific Annotations
Composed with
Platform
Component-Based Containers Conforms to
Hulotte Metamodel
Dispatcher
Connector
Event Mgmt
Component activation
2
Architecture reification
Reconf
Are implemented by
Resources Mgmt
Operating system
Composed with Component-Based RTOS Conforms to
Scheduler
Task
File System
Semaphore
Interrupts Mgmt
Memory Mailbox
Fig. 2. A Three-Tier Approach for Composition of a RTE Software Stack
(a) Implemented by the application developer Component-Based Application
Hulotte frontend
Annotated CB Application
1 Domain-Specific Annotation definitions
Component-Based Containers
Applicative components composed with their dedicated containers
2
Applicative components and containers composed with RTOS
Final optimized architecture
Component-Based RTOS
(b) Implemented by the platform developer
Hulotte backend
C files
Executable
(c) Handled automatically by the Hulotte Framework
Fig. 3. Description of the Hulotte’s Design Process
are integral parts of the Hulotte framework and will be detailed in Section 5. Containers implement non-functional services required by the applicative components, control them, mediate their interactions with other applicative components and with the operating system. Therefore, as a second stage of composition (Fig. 2(2)), the operating system services required by the containers are bound to the component-based operating system presented in Section 5. A detailed description of the roles involved within the design process presented in this paper is sketched out in Figure 3, where steps (1) and (2) refer to the same two composition steps from Figure 2. 3.2
Specificities of RTE Audio Applications
In this paper, we present our approach in order to provide to the application developer a design space for component-based audio and music applications. These applications are inherently designed as multitask and concurrent softwares since
42
F. Loiret et al.
they generally implement audio flows processing algorithms controlled by the end-user via HMIs (Human-Machine Interfaces). Moreover, they must be executed in a real-time context since audio data must be processed in time to avoid buffer underflows. Therefore, the developer must properly manage the resources of the system (tasks, audio buffers, mutex, etc), implement the audio data copies throughout, for example, a pipe of audio filters – potentially at different sample rates – or the way tasks are synchronized and shared data are protected within critical sections. These aspects are typical domain-specific concerns since from one application to another, their implementations are redundant, timeconsuming and error-prone. Therefore, the aim of Hulotte is to provide a design space where these concerns are handled automatically by our framework. 3.3
Case Study: A Vinyl Multimedia Controller
To illustrate the main features of our approach, we introduce our case study (DeckX) on which we will rely throughout the paper: An application for DJ’s that uses a special vinyl record as a mean of controlling various multimedia sources (such as MP3 audio or video files) from a classical turntable. It operates as follows: the vinyl is pressed with a dedicated sinusoidal stereo signal which encodes a “time-code”. A software analysis of the signal gives three pieces of information: the absolute position of the turntable arm in the vinyl, its velocity and its rotation direction. Concretely, it is thus possible to “scratch” in realtime MP3 files stored in a computer from the turntable. Moreover, we consider the ability for the DJ to control audio parameters (output volume and filtering parameters) from the keyboard. Our case study thus represents a real-life application, and is composed of various functional parts which have to meet various concurrent constraints. Moreover, the application is intended to be deployed in a microcontroller and must therefore fulfill constraints encompassed by ressource limited devices.
4
Application Level: Designing RTE Audio Applications
This section presents the domain-specific annotations provided to the application developer for designing real-time and audio applications. We outline then how DeckX is implemented with Hulotte. 4.1
Domain-Specific Annotations
The audio-domain-specific concerns presented in Section 3.2 are modeled by Hulotte annotations. These annotations are used by the application developer to specialize its application specified as a set of interconnected applicative components. The list of annotations provided to the developer is given in Table 1 and their basic semantics are detailed below. @ClientServerItf and @AudioItf annotations specify the signatures and properties of the interfaces exported by the components. A Client-server interface signature defines a set of method signatures (like Java interfaces) with a list
A Three-Tier Approach for Composition of RTE Software Stacks
43
Table 1. Hulotte Annotations Dedicated to Real-Time Audio Applications Annotation @ClientServerItf
@AudioItf
@Buffered @MonoActive @MultiActive @Asynchronous @Protected @CpuItHandler @OSItf
Applied to
Parameters
signature: string role:{client | server} cardinality:{single | multicast} interface signature: string role:{producer | consumer} cardinality:{single | multicast} interface bufferSize: integer interface|component priority: integer interface|component priority: integer poolSize: integer binding component initialValue: integer interface irqNumber: integer interface interface
of parameters and a return type. The parameters of the annotation specify if the interface is client or server, single or multicast. The multicast property specifies a one-to-N connection scheme between one client and N server interfaces. Client-server interfaces model services required or provided by the components. The audio interfaces (i.e., the interfaces annotated with @AudioItf) model streams of audio data produced or consumed by the components. Their signatures define a set of parameters qualifying audio streams: the number of audio frames transmitted, their data types (an audio frame is stored as an integer, a float or a double), the number of audio channels (e.g. mono or stereo), the sample rate of the audio flow, and the way multiple channel frames are intertwined. The need to qualify these audio interface signatures is justified for composing independently implemented components. Indeed, interconnected components may have been implemented according to audio streams encoded differently. An example of a client-server and of an audio interface signatures using the Hulotte’s IDL (Interface Description Language) is given in Figure 4. The bindings between applicative components are specified between a client and a server interface or between a producer and a consumer audio interface. public cltsrvinterface deckX . api . Track { struct_track_t * getTrack (void); int track_import (char * path ); int track_handle (void); }
public audiointerface audio . api . An a l y s e r A u d i o T y p e { audio_frame_size : 64; sample_type : f l o a t ; channels : 2; sample_rate : 44100; i n t e r t w i n e d _ s a m p l e s : true; }
Fig. 4. IDL’s Examples of a Client-Server and Audio Interface Signatures
@Buffered annotations can be applied to interfaces. Such an interface specifies that service invocations or audio streams passing through it are buffered. The size of the buffer is specified by the bufferSize parameter of the annotation. Buffered interfaces are for instance used when components produce and consume audio streams at different frequencies.
44
F. Loiret et al.
The @MonoActive annotation specifies that an applicative component is attached to its own thread of execution defined with a priority. The execution model attached to such a component performs the incoming activation requests sequentially – i.e., in a run-to-completion mode – with a FIFO ordering policy. In our case, activation requests can be operation invocations from a server interface or audio streams consumed from an audio interface. The @MultiActive annotation has the same semantics but for a pool of threads performing activation requests in parallel. The @Asynchronous annotation can be applied to bindings. In this case, the thread of control originating from the source interface of the binding returns without being blocked until the completion of the execution at destination interface side. If the implementation code of an applicative component is stateful and not reentrant (i.e., it can not be safely executed concurrently), the developer uses the @Protected annotation. In this case, the Hulotte execution framework guarantees mutual exclusion: a single execution flow is allowed to execute the code encapsulated by such a protected component (just as the synchronized keyword in Java). @CpuItHandler and @OSItf are used to specify a link between the application layer and the operating system layer. Indeed, two cases are identified: First, an applicative component’s execution may be triggered by the reception of a hardware interrupt (@CpuItHandler) managed by the operating system. Second, an applicative component may directly require services implemented by the operating system or device drivers not directly handled by the application (@OSItf). In these two cases, the annotated interfaces will be automatically bound afterwards by the Hulotte process as described in Section 5. 4.2
Annotated Architecture of DeckX
The Hulotte architecture of our case study is given in Figure 5. In the following, we outline its functional and non-functional behavior, the latter being specified by annotations: The AudioDriver component will be attached at runtime by a thread of execution activated by an interrupt managed by the operating system. This driver reads the audio buffers from the audio device (corresponding to the timecoded signal read from the turntable) and produces asynchronously audio frames on its outgoing audio interface. The Analyser1 component is also attached to its own thread of execution and is activated each time new audio frames are buffered in its incoming audio interface. At each analysis step, timecode information decoded from incoming audio frames are multicasted to the GUI and the AudioPlayerDeck1 components. According to this information, the Player component processes audio frames read from an MP3 file managed by the Track component, sends them to the AudioFilters and finally to the incoming audio interface of the AudioDriver. The KeyboardDriver component is activated each time a key is pressed by the end-user and dispatch the process of the corresponding event to change a parameter of the AudioFilters or to load a new MP3 track via the Track component (the latter thus requires an
A Three-Tier Approach for Composition of RTE Software Stacks
45
OSItf to access to the file system managed by the operating system layer). The latter is protected since concurrent execution flows initiated from the Analyser1 or the KeyboardDriver components access to its provided services. The priorities of the @MonoActive and @MultiActive annotations attached to applicative components are specified as follows: Prio(AudioDriver) > Prio(Analyser1) > Prio(KeyboardDriver) > Prio(GUI), since audio samples must be handled in priority by the AudioDriver and the analysis process, compared to the interactions with the end-user with the keyboard and the GUI. Annotations are implemented by the Hulotte platform as it is presented in the next section. @MonoActive
@Asynchronous
Timecode DataBase
@Asynchronous @MonoActive @CpuItHandler
AudioDriver
DeckX
GUI @Asynchronous
AudioPlayerDeck1
@Buffered
Player
Analyser1
@Protected
Track
@MonoActive @Buffered
AudioFilters @MultiActive @CpuItHandler
Volume Filter
@OSItf
Effects Filter
Keyboard Driver
Legend
@domain-specific annotation
Interfaces annotated by @ClientServerItf
Interfaces annotated by @AudioItf
Primitive component
Composite component
1 component DeckX { 2 component A n a l y s e r 1 { 3 @Buffered( b u f f e r S i z e= " 512 " ) 4 @MonoActive( priority = " 20 " ) 5 @AudioItf( sign = " audio . api . A n a l y s e r A u d i o T y p e" , role = " consumer " , 6 c a r d i n a l i t y= " single " ) 7 d e s t i n a t i o n interface i n p u t A u d i o 9 10 11
@ClientServerItf ( sign = " deckX . api . TimeCodeType , role = " client " , c a r d i n a l i t y= " m u l t i c a s t" ) source i n t e r f a c e o u t p u t T i m e c o d e
13 14 15
@ C l i e n t S e r v e r I t f( sign = " deckX . api . tcDBType " , role = " client " , c a r d i n a l i t y= " m u l t i c a s t" ) source i n t e r f a c e t c D a t a B a s e
17 18 19 20 21 22 23 }
content A n a l y s e r I m p l. c } @Asynchronous binds A u d i o D r i v e r. o u t p u t A u d i o to A n a l y s e r 1. i n p u t A u d i o binds A n a l y s e r 1. t c D a t a B a s e to T i m e c o d e D a t a B a s e. t c D a t a B a s e // ... Other bindings and c o m p o n e n t s
Fig. 5. Graphical and Textual Representations (excerpt) of a DeckX’s Annotated Architecture
46
5
F. Loiret et al.
Hulotte Platform and Operating System Compositions
As sketched out in Figure 3(b), the platform developer is responsible for implementing the runtime platform supporting the domain-specific requirements specified as Hulotte annotations. The Hulotte platform is engineered with component-based containers, which brings three significant advantages: i) the platform developer benefits from a component-based design to implement the semantics of arbitrary complex domain-specific annotations, in a decoupled way from the application logic, ii) our approach relies on a reflective architecture, in a symmetric and isomorphic way, and iii) the low-level services required by the platform and provided by the operating system are explicitly specified. The concept of container on which the platform is built is generalized, defining composition rules and architectural invariants as architectural patterns to specify the link between applicative components and platform components as it is presented in the following section. The component-based implementation of the operating system is detailed in Section 5.2, as the composition between the container and the OS layers. 5.1
Component-Based Containers Design and Composition
The composition of the containers corresponds to the first composition step of the process depicted in Figure 3(c), and relies on a generative and aspect weaving technique. Each Hulotte annotation is implemented by the platform developer as architectural fragments made of interceptor and platform components. The platform developer implements a Hulotte plugin which provides the way these fragments will be woven into composite containers, according to the annotations specified at the application level. The output of this composition step is an architecture description where all applicative components are encapsulated within their dedicated containers. Therefore, for a given applicative component, its container: – implements non-functional services it requires via annotations, – mediates the domain-specific interactions with its environment, – manages the resource instances it requires, such as tasks, semaphores, buffers or message queues, reified as components [14], – allows inversion of control based on interception of execution flows transiting via applicative component’s interfaces. The platform components are generic components provided by the Hulotte component library, or generated components. The architectural specification and implementations of the latter are respectively generated programmatically and by source code templates according i) to interface signatures of the applicative components they will control, and ii) the annotations parameters set by the application developer. Interceptors are also generated since their specifications rely on applicative components’ interfaces always known only at weaving time. According to the annotations presented in Table 1, the contents of the containers depend on the following information which are specified by the application developer:
A Three-Tier Approach for Composition of RTE Software Stacks
47
– Buffered interfaces are intercepted, and data transiting through them (e.g., parameters of method or audio flows) are stored in a buffer implemented as a platform component. – The semantics of MonoActive and MultiActive components are implemented by OS task instances handled by generated platform components. The latter control their thread(s) of execution as it was mentioned in Section 4.1. – The logics behind Asynchronous bindings and multicast interfaces are implemented by interceptors. – The semantics of Protected component is implemented by interceptors, all together bound to a semaphore. The counter of the semaphore is then incremented when an execution flow from the environment of the protected component execute a service it provides and is decremented when it returns. – In the case of applicative components bound via audio interfaces with different signatures, a dedicated interceptor is generated implementing the conversion algorithm between the source and the destination of the audio flow. As an example, Figure 6 shows the container of the Analyser1 applicative component, according to the annotations specified by the application developer presented in Figure 5. Note that Hulotte annotations are also defined to characterize the specificities of the container level concepts. To TimecodeDataBase container
To AudioPlayerDeck1 container
From AudioDriver container @Interceptor @Generated AudioFlow converter interceptor
To GUI container
@Interceptor @Generated
@Interceptor @Generated
PushAudio interceptor
@PlatformComp @PlatformComp @Generated
WriteFreeReadLock Ringbuffer
Ringbuffer
@PlatformComp
Multicast interceptor
Analyser1
Semaphore instance
@OSItf
MonoActive controller
@PlatformComp
Task instance
@OSItf
Analyser1 Container
Fig. 6. The Generated Container of the Analyser1 Applicative Component
Within this container, audio flows coming from the AudioDriver component are converted according to the flow parameters expected by Analyser1 (specified by the signature of its incoming audio interface) and are buffered by the PushAudio interceptor. The multicast client interface named outputTimecode (see Fig. 5 lines 9-11) and bound to GUI and AudioPlayerDeck1 components is handled by the Multicast interceptor. The semantics of the Asynchronous,
48
F. Loiret et al.
MonoActive and Buffered annotations attached to the incoming binding and interface of the Analyser1 component (see Fig. 5 lines 3-7 and 19-20) is implemented by the set of platform components encapsulated within the container. 5.2
RTOS Design and Composition
As it has been presented, Hulotte containers implement non-functional concerns required by the applicative components. These concerns may require operating system services, such as task scheduling, time or Inter-Process Communications management. In this sense, containers act also as an intermediate abstraction layer between the operating system and the application layers. As it is presented in details in [14], our component-based RTOS consists of a set of primitive components encapsulated within a composite. The latter exports the public services invokable from container and applicative components. Within the Hulotte process depicted in Figure 3, the second composition step consists in a two-direction composition between these layers, via client-server bindings: – From container (or applicative) components to the RTOS. In this case, client interfaces annotated by @OSItf annotation are bound to the corresponding interface of the RTOS. – From the RTOS to the container components when the latter encapsulates applicative components annotated by @CpuItHandler. In this case, applicative components export handlers to serve hardware’s interrupts. This composition step is automatically handled by Hulotte and is based on the signatures exported by the interfaces. The content of the RTOS is therefore automatically built according to the services strictly required by the applicative and container components.
6
Evaluation
In this section, we provide a detailed evaluation of our approach, from a qualitative and a quantitative point of view. 6.1
Qualitative Evaluation
Application’s Design Space. Hulotte provides a component-based design space which enforces a strong separation of concerns. Indeed, the developer is exclusively focused on the implementation of its applicative architecture, afterwards annotated with domain-specific/non-functional concerns. In consequence, this separation occurs also at code level, the latter becoming more readable and maintainable – reflecting the functional needs of the application without any constraints imposed by the low-level real-time audio properties, as it has been experimented with DeckX. Moreover, the decoupling between the architecture and the annotations improves reuse since components can be independently deployed in various execution contexts without any applicative code refactoring.
A Three-Tier Approach for Composition of RTE Software Stacks
49
However, since we propose a generic mechanism where each architectural artifact can be annotated by arbitrary annotations, their use imposes several constraints for the application developer. Indeed, annotations may be applied incorrectly to an artifact, a set of annotations can be self-contradictory and cannot be composed together, or annotations can depend on each other. To tackle with issue, we chose a defensive approach based on constraints (using the OCL constraint language [19]) which must be explicitly specified by the platform developer, and which are checked automatically from an annotated application. These checks operate just before the container generation step and ensure the consistency of the application’s specifications. These points are reported in details in [18]. Platform’s Design Space. The Hulotte platform consists of a set of containers implementing real-time-audio annotations. The containers composition process relies on the incoming and outgoing interaction points externalized by the applicative components, through explicitly defined and stable interfaces, and therefore independently of their implementations. Moreover, a strong separation of concerns is applied between applicative and platform components which are linked together by composition without any dependency on the internal elements of the applicative code. These characteristics of our approach allow us to improve sorely the extensibility of the platform, towards the support of new nonfunctional concerns. For instance, our component-based platform model has been validated in studies spanning various domains, from distributed reflective and reconfigurable applications [13], to Real-Time Java Specifications (RTSJ) [21]. Taking Benefits of a Full Component-Based Approach. We can witness several benefits in using a homogeneous component model for constructing RTE software stack, made of applicative, platform, and RTOS components. First, we rely on homogeneous composition techniques based on the component requirements exposed at architectural level to obtain the final stack. As it has been presented in Section 5.2, this feature allows us to built automatically an operating system fitting exactly the services required by the whole system. Second, since the Hulotte process outputs a flattened architecture of the complete software stack, we can apply uniformly tools based on the abstractions of our component model. Two features provided by Hulotte are presented and evaluated thereafter: the capability to support introspection and reconfiguration of the system at runtime and the support of optimization techniques of the final executable. The latter is a mandatory requirement raised from the embedded domain, since relying on high-level abstractions at design time must not impact drastically on performance at runtime. 6.2
Quantitative Evaluation
As illustrated in Figure 3, the Hulotte framework consists of a frontend implementing the composition steps described in Section 5 and a backend. In this
50
F. Loiret et al.
paper, the backend relies on the Think component framework [5]. From the flattened architecture outputed from the frontend, the backend generates a set of C source files compiled afterwards by a classical C compiler. In the following section, we measure how our approach impacts the resulting executable in terms of memory footprint and execution time, based on the DeckX case study. The comparisons are established between a reference implementation against a component-based design, the latter being based on Hulotte. The reference implementation corresponds to a version of DeckX where applicative functionalities and these implemented by the Hulotte containers are implemented manually in a full code-centric approach, and linked to a monolithic implementation of the operating system. In both cases, the same set of functionalities are embedded in the final binary. For the component-based design, we consider three scenarios: i) Flexible, where all components outputed from the Hulotte process are generated as introspectable and reconfigurable entities at runtime. This feature is supported by Think, which generates meta-data and provides these capabilities at runtime. ii) Not flexible, which generates a static binary of the whole architecture, not introspectable and reconfigurable anymore. This scenario relies on Think optimizations described in [12,14] to control performance overheads induced by the backend framework. Finally, iii) the Flattened scenario, consisting of a binary generated as static and without hierarchical encapsulation. These scenarios are taken automatically into account within the last stage of the Hulotte frontend process depicted on Figure 3, according to the developer preferences. Memory Footprint. Figure 7 presents the memory footprints of the reference implementation and the component-based designed for the three aforementioned scenarios2. We measure the overhead in code (i.e., .text section) and data, including initialized (i.e., .data section) and uninitialized (i.e., .bss section) data. We make this distinction as code is usually placed in ROM, whereas data are generally placed in RAM. The overheads are not Reference Component-Based Design (a) (b) (c) (d) negligible for the Flexible sceFlexible Not flexible Flattened nario (Fig. 7(b)), reflecting the Code 26994 +20.0 % +5.9 % 2.1 % +0 % cost to provide a full reconfigu- Data 17320 +12.0 % +0 % ration support at runtime. HowFig. 7. Memory Footprint Sizes of DeckX (in ever, since this feature may not be Bytes) and Overheads required for the whole embedded stack, Hulotte relies on the mechanisms provided by Think to specify whether a single component or a subset of the architecture should be generated as reconfigurable [14]. When considering a complete static system (Fig. 7(c)), just a code overhead is observed, becoming negligible in the last scenario (Fig. 7(d)). The latter overhead comes for the resource instances reified as components within our approach. 2
These experiments have been conducted using GCC with the -Os option that optimizes the binary image size.
A Three-Tier Approach for Composition of RTE Software Stacks
51
The Figure 8 presents the memory Total Abstraction Layers footprints for the Flattened scenario (a) (b) (c) (d) RTOS Platform Application compared between the RTOS, the platCode 27558 41.1 % 19.5 % 28.6 % form and the application layers3. These Data 17320 86.2 % 9.8 % 2.7 % 11 27 14 results show in particular the impor- Comps tant part of DeckX related to audio and real-time concerns, which are au- Fig. 8. Memory Footprint Sizes (in Bytes) and number of components of tomatically handled by our approach DeckX for each Abstraction Layer (Fig. 8(c)) in an oblivious manner for the application developer. Execution Time. Finally, Figure 9 presents the execution time overheads involved by our approach based Ref. Component-Based Design on the longest execution path (a) (b) (c) (d) of DeckX, traversing more than Flexible Not flexible Flattened forty components from the appli- Mean (µs) 176.0 +2.9 % +1.7 % +0.3 % 7.2 7.3 7.2 cation level as well as the container Std. dev. 7.2 and RTOS levels. The testing en- Fig. 9. Execution Time Overheads (and Stanvironment consists of a Pentium dard Deviation) 4 monoprocessor at 2.0 GHz, running 100,000 times the execution path4 . These results show that the involved overheads are completly negligible for the Flattened scenario (Fig. 9(d)) and acceptable in the other cases (Fig. 9(b) & (c)).
7
Related Work
Specializing Component Models with Annotations. In programming languages, the use of annotations is widely applied to specialize their basic constructs. However, to the best of our knowledge, only the Think ADL [11], which we drew our inspiration from, and UML2 [20] exploit this feature to specialize architectural constructs. However, with Think, their uses are limited to optimization properties, configuring the last stages of the Think’s code generation process, and not to refine the applicative architecture with other non-functional concerns. In turn, UML2 defines the composite structure diagram for specifying software architectures, and introduces the notion of profiles [8]. The latter is the builtin lightweight mechanism that serves to customize UML artifacts for a specific domain or purpose via stereotypes. Thus, the latter could be used to extend the semantics of the composite structure diagram artifacts. Our approach shares with UML the notion of annotation, close to the one of a stereotype. 3 4
The total does not equal to 100% due to some code and data generated in the binary by the linking process of the C compiler. The scenario was “simulated” under a Linux 2.6 kernel (using a Linux port of µC) patched by Rt-Preempt. The latter converts the kernel into a fully preemptible one with high resolution clock support, allowing precise performance measures.
52
F. Loiret et al.
Extensible Container-Based Approaches. Even if component containers – originating for the EJB – are a key part of component frameworks, they generally support a predefined set of non-functional concerns. On the contrary, the PIN component model [17] is based on generative techniques to generate custom containers encompassing component interactions and implementing non-functional concerns. A strong separation of concerns is preserved between the latter and the code implemented by applicative components. Despite these similarities with our contribution, PIN relies on a code-centric approach (based on AspectC++) for generating containers, whereas Hulotte capitalizes on the component paradigm at this level. In this respect, Asbaco [16] and AoKell [23] are similar to our approach, both targeting the Java language for IT systems. However, they rely on costly mechanisms such as load-time mixin technique based on bytecode generation not suitable for embedded systems, and do not consider applications constrained by time and requiring low-level services from the operating system. Handling Flexibility in Embedded Software Stacks Based on CBSE. Considering the approaches targeting real-time embedded systems, CBSE has been adopted either at operating system level [7,10,15,22] or to propose Architecture Description Languages capturing the domain’s relevant abstractions [1,6,9,24]. In the first case, CBSE is exploited to provide a set of components used as building blocks to configure an operating system. However, within these approaches, the applicative components directly use the services provided by the OS, without any intermediate and flexible layer implementing non-functional concerns in an oblivious manner for the application developer. On the contrary, software stacks provided by the mentioned ADLs propose domain-specific abstractions implemented by a dedicated runtime, but do not provide an engineering of this layer to support new features not initially supported by their languages.
8
Conclusion
As embedded systems must be constantly adapted to support new features with a growing complexity, it is becoming necessary to use current software engineering principles and methodologies to increase software productivity. CBSE is widely-known to foster separations of concerns, reuse, and flexibility to shorten development time. This paper presents a three-tier approach for composition of flexible realtime embedded software stacks. It relies on a design process where flexibility is achieved by two features: i) an annotation-based mechanism for specializing the application’s architectural artefacts with non-functional concerns, and ii) a container model for the generation and composition of the runtime platform implementing them. The extensible nature of the containers makes them suitable for implementing complex features supported by the platform, relieving the application developer from dealing with redundant and error-prone tasks. Containers offer a straightforward design space for adapting the platform towards various application requirements. Moreover, our software stack model relies on
A Three-Tier Approach for Composition of RTE Software Stacks
53
a component-based RTOS which provides, by composition, the low-level services strictly required by the upper layers. In this paper, we apply our approach for designing and implementing real-time audio applications. We demonstrate through a real-life case study that the impact on performances of our design process is negligible. As a future work, we plan to provide a richer library of annotations encompassing various communication models and execution models (e.g., Constant Bandwidth or Contract-Based Scheduling Servers) commonly used in RTE systems. Moreover, we envision to extend the generic component model on which the software stack relies with a behavioral model based on automata. The composition of these automata gives the global behavior of the whole stack outputed by our process, including low-level OS primitives, analyzable by model-checking tools (such as deadlock-free analysis).
Acknowledgment This work was supported by the french Minalogic Mind project.
References 1. Automotive Open System Architecture (AUTOSAR), http://www.autosar.org 2. MicroC/OS-II: The Real-Time Kernel. CMP Media, Inc., USA (2002) 3. Bruneton, E., Coupaye, T., Leclercq, M., Qu´ema, V., Stefani, J.-B.: The Fractal Component Model and its Support in Java: Experiences with Auto-adaptive and Reconfigurable Systems. Software Practice & Experience 36(11-12), 1257–1284 (2006) 4. Szyperski, C.: Component Software: Beyond Object-Oriented Programming, 2nd edn. Addison-Wesley, Reading (2002) 5. Fassino, J.-P., Stefani, J.-B., Lawall, J., Muller, G.: THINK: A Software Framework for Component-based Operating System Kernels. In: Proceedings of the USENIX Annual Technical Conference, June 2002, pp. 73–86 (2002) 6. Feiler, P.H., Lewis, B., Vestal, S., Colbert, E.: An overview of the SAE Architecture & Design Language (AADL) Standart: A Basis for Model-Based ArchitectureDriven Embedded Systems Engineering. In: Architecture Description Language, workshop at IFIP World Computer Congress (2004) 7. Ford, B., Back, G., Benson, G., Lepreau, J., Lin, A., Shivers, O.: The Flux OSKit A Substrate for Kernel and Language Research. In: Proceedings of the sixteenth ACM symposium on Operating Systems Principles, pp. 38–51 (1997) 8. Fuentes, L., Vallecillo, A.: An Introduction to UML Profiles. UPGRADE, The European Journal for the Informatics Professional, 5–13 (April 2004) 9. Hansson, H., Akerholm, M., Crnkovic, I., Torngren, M.: SaveCCM - A Component Model for Safety-Critical Real-Time Systems. In: EUROMICRO 2004: Proceedings of the 30th EUROMICRO Conference, Washington, DC, USA, pp. 627–635. IEEE Computer Society, Los Alamitos (2004) 10. Levis, P., Madden, S., Polastre, J., Szewczyk, R., Whitehouse, K., Woo, A., Gay, D., Hill, J., Welsh, M., Brewer, E., Culler, D.: TinyOS: An Operating System for Sensor Networks. Ambient Intelligence, 115–148 (2005)
54
F. Loiret et al.
11. Lobry, O., Navas, J., Babau, J.-P.: Optimizing Component Based Embedded Software. In: Int. Conf. on Computer Software and Applications, vol. 2, pp. 491–496 (2009) 12. Lobry, O., Polakovic, J.: Controlling the Performance Overhead of ComponentBased Systems. In: Software Composition, pp. 149–156. Springer, Heidelberg (2008) 13. Loiret, F., Malohlava, M., Plˇsek, A., Merle, P., Seinturier, L.: Constructing DomainSpecific Component Frameworks through Architecture Refinement. In: 35th Euromicro Conf. on Software Engineering and Advanced Applications (SEAA 2009), August 2009, pp. 375–382 (2009) 14. Loiret, F., Navas, J., Babau, J.-P., Lobry, O.: Component-Based Real-Time Operating System for Embedded Applications. In: Lewis, G.A., Poernomo, I., Hofmeister, C. (eds.) CBSE 2009. LNCS, vol. 5582, pp. 209–226. Springer, Heidelberg (2009) 15. Massa, A.: Embedded Software Development with eCos. Prentice-Hall, Englewood Cliffs (2002) 16. Mencl, V., Bures, T.: Microcomponent-Based Component Controllers: A Foundation for Component Aspects. In: Asia-Pacific Software Engineering Conf., pp. 729–737 (2005) 17. Moreno, G.A.: Creating Custom Containers with Generative Techniques. In: 5th Int. Conf. on Generative Programming and Component Engineering (GPCE 2006), pp. 29–38. ACM, New York (2006) 18. Noguera, C., Loiret, F.: Checking Architectural and Implementation Constraints for Domain Specific Component Frameworks using Models. In: 35th Euromicro Conf. on Software Engineering and Advanced Applications (SEAA 2009), August 2009, pp. 125–132 (2009) 19. OMG. UML 2.0 Object Constraint Language (OCL) Specification 20. OMG. Object Management Group: Unified Modeling Language – Superstructure Version 2.1.1 (2007) 21. Plˇsek, A., Loiret, F., Merle, P., Seinturier, L.: A Component Framework for Java-based RealTime Embedded Systems. In: Proceedings of the 9th ACM/IFIP/USENIX International Conference on Middleware (Middleware 2008), Leuven, Belgium, December 2008, pp. 124–143. Springer, Heidelberg (2008) 22. Reid, A., Flatt, M., Stoller, L., Lepreau, J., Eide, E.: Knit: Component Composition for Systems Software. In: Proc. of the Fourth Symposium on Operating Systems Design and Implementation, pp. 347–360 (2000) 23. Seinturier, L., Pessemier, N., Duchien, L., Coupaye, T.: A Component Model Engineered with Components and Aspects. In: Gorton, I., Heineman, G.T., Crnkovi´c, I., Schmidt, H.W., Stafford, J.A., Szyperski, C., Wallnau, K. (eds.) CBSE 2006. LNCS, vol. 4063, pp. 139–153. Springer, Heidelberg (2006) 24. van Ommering, R., van der Linden, F., Kramer, J., Magee, J.: The Koala Component Model for Consumer Electronics Software. Computer 33(3), 78–85 (2000)
Bridging the Semantic Gap between Abstract Models of Embedded Systems Jagadish Suryadevara, Eun-Young Kang, Cristina Seceleanu, and Paul Pettersson M¨ alardalen Real-Time Research Centre, M¨ alardalen University, V¨ aster˚ as, Sweden {jagadish.suryadevara,eun.young.kang,cristina.seceleanu, paul.pettersson}@mdh.se.
Abstract. In the development of embedded software, modeling languages used within or across development phases e.g., requirements, specification, design, etc are based on different paradigms and an approach for relating these is needed. In this paper, we present a formal framework for relating specification and design models of embedded systems. We have chosen UML statemachines as specification models and ProCom component language for design models. While the specification is event-driven, the design is based on time triggering and data flow. To relate these abstractions, through the execution trajectories of corresponding models, formal semantics for both kinds of models and a set of inference rules are defined. The approach is applied on an autonomous truck case-study.
1
Introduction
Embedded systems (ES) are increasingly becoming control intensive, and time sensitive. To ensure predictable behaviors, the development phases of an ES require extensive modeling and analysis. These development phases/ abstraction layers e.g., requirements, specification, design, and implementation, provide opportunities for applying different predictability analysis techniques. Such models have to be precise enough to support formal analysis, and must ensure inter-operability during design. However, they may use paradigms for describing behavior that cannot be immediately compared and related, due to their apparently incompatible nature. There exist several paradigms for behavior specification of embedded systems. For example, statemachine based approaches, such as UML statemachines [11], are intended to specify timed aspects of computation and communication, besides functionality. They often use an aperiodic, event-triggered representation of behavior, since such a paradigm facilitates easy changing of a model’s configuration or set of events. On the other hand, behavior models might use a different
This work was partially supported by the Swedish Foundation for Strategic Research via the strategic research centre PROGRESS and Q-ImPrESS EU project.
L. Grunske, R. Reussner, and F. Plasil (Eds.): CBSE 2010, LNCS 6092, pp. 55–73, 2010. c Springer-Verlag Berlin Heidelberg 2010
56
J. Suryadevara et al.
modeling paradigm, e.g., a periodic, time-triggered behavioral description, instead of an event-triggered representation. With time-triggered communication, the data is read from a buffer, according to a triggering condition generated by, e.g., a periodic clock. Although these modeling capabilities are invaluable to obtaining a mature ES development process tailored for predictability, in order to ensure the correctness of the process, one needs to guarantee that the behavioral models are indeed consistent. In this paper, we present a formal framework and a methodology for relating event-based and time triggered, data-flow driven models of behavior, which may be used at the same abstraction layer, e.g., at specification level, or across various layers of abstraction, from specification, to, e.g., the design level of embedded system development. Concretely, we consider UML statemachines [11] for eventbased specification models and the ProCom component language [15] for design models. Hence, as it stands now, the framework is tailored to a specific class of embedded systems, which employ the above mentioned formalisms for modeling behavior. However, the framework and the methodology could be generalized to include other similar classes of systems (e.g., component based systems) and other behavioral paradigms (e.g., finite state machines). The proposed framework is based on comparison of execution trajectories of corresponding behavior models. To accomplish this, the formal semantics of both kinds of models is defined in terms of underlying transition systems. As the execution trajectories generated by above described models can be extremely large and incomprehensible, they need to be reduced to more readable and analyzable forms. Hence, we propose two sets of inference rules, one for simplification of specification trajectories and other for simplification of design trajectories. Moreover, in order to be able to relate and compare the above two sets of simplified trajectories, we introduce a set of transformation rules that lets one relate an event-triggered trajectory with corresponding time-triggered one. We apply our approach on an autonomous truck system, by comparing some trajectories of its specification with those of corresponding component-based design model. By virtually simulating the models, we show a “run” of each model, respectively, by outlining corresponding sets of representative trajectories. Then, we show that, by applying our rules, we can first simplify the design model trajectory and then transform it into a trajectory equivalent to the one generated by the specification model. The timing aspects of both runs are also apparent in the respective trajectories, hence we show how to relate them too. For creating the truck’s design model, we use the development environment of SaveIDE [12], an integrated design environment for ES. SaveIDE is developed as part of the PROGRESS project [1] for component-based development of predictable ES in the vehicular domain. It supports the subset of ProCom modeling language used for the case study design of the paper. The rest of the paper is organized as follows. In Section 2, we describe eventbased, and time triggered formalisms for modeling embedded systems. Corresponding to these formalisms we formally define semantics of a subset of both
Bridging the Semantic Gap between Abstract Models of Embedded Systems
57
UML statemachines and ProCom design languages. In Section 3, we present the case study details. In Section 4, we describe our methodology, and introduce three sets of inference rules for simplification and comparison of trajectories of specification and design models. Some related work is discussed in Section 5. In section 6, we make conclusions and some aspects of the future work of the paper.
2
Abstract Models of Embedded Systems
In this section, we define the modeling formalisms for model-based specification and design of embedded systems used in this paper. As specification language, we will consider UML statemachine notation with timing annotations [11], and for design models, we will use the ProCom component modeling language [3]. 2.1
Specification Model of Embedded Systems
We specify embedded systems using the UML statemachine notation [11]. In order to model timing, we will use the notion of timeouts provided in UML. An example of a UML statemachine is shown in Fig. 1. We now give a formal definition of the model: Definition 1 (Statemachine Syntax). A statemachine is a tuple L, l0 , A, E , M where – L is a finite set of locations, – l0 ∈ L is the initial location, – A = {a0 , ..., an , tm} is a set of events, where • ai is an external event with zero or more parameters, • tm is a timeout representing the expiry of a timer, and – M : L → {ε} ∪ N is a mapping from locations to the natural numbers (including zero), or ε denoting absence of timeout, – E ⊆ L × A × L is a set of edges. Fig. 1 shows a UML statemachine with the three locations Follow, Turn, and Find. The edges from Follow to Turn and from Find to Follow are labeled with the external events e o l() and line found(), respectively. The edge from Turn to Find is labeled with event after(4), intuitively denoting a timeout that expires after four time units1 . We now give the semantics of a UML statemachine specification model defined in terms of a finite state transition system. Definition 2 (Statemachine Semantics). The semantics of a statemachine is defined as a transition system S , s0 , T where – S is a finite set of states of form l , m with l ∈ L and m ∈ {ε} ∪ N, 1
In the figures, we use timeout events of the form after (n), where n ∈ N, instead of annotating the source location (e.g., location Turn in Fig. 1) with timeout value n.
58
J. Suryadevara et al.
Turn e_o_l() after(4)
Follow line_found()
Find Fig. 1. A UML statemachine specification model of the autonomous truck
– s0 ∈ S is the initial state l0 , M (l0 ), – T ⊆ S × A ∪ {tick } × S where tick is a periodic internal event, is a transition relation such that ai • l , m −→ l , m if l , ai , l ∈ E , and m = M (l ) ε if m = ε tick = 0, and m = • l , m −−→ l , m if l = l , m
m − 1 otherwise tm
• l , m −−→ l , m if l , tm, l ∈ E , m = 0, and m = M (l )
Intuitively, the initial state represents the initial location, and its timeout value, in the statemachine. The first rule describes the state change when an external event specified over an edge from current location, and in the current state, occurs. By second rule, if a timeout is defined at current location, the current value of the timeout decreases in steps of one corresponding to each occurrence of an internal periodic tick event. The tick event is ignored in the current state if no timeout is associated with the corresponding location. The third rule describes the occurrence of timeout event, and hence the location and corresponding state change, when the timeout duration associated with the current location expires i.e. becomes zero. A trajectory of a UML specification model is an infinite sequence λ
λ
0 1 τ = l0 , m0 −→ l1 , m1 −→ l2 , m2 ...
λ
i li+1 , mi+1 ∈ T and λi ∈ where l0 , m0 is the initial state, and li , mi −→ {a0 , ..., an , tick , tm} for all i ∈ N.
2.2
Design Model of Embedded Systems
As design modeling language we will use ProCom [3], a component model for embedded systems. It consists of the two sub-languages: ProSys, which is designed
Bridging the Semantic Gap between Abstract Models of Embedded Systems
59
to model systems at high level (i.e., in terms of large-grained components called subsystems), and ProSave [15] which is designed to model detailed functionality of the subsystems. In this paper, we will focus on the ProSave model as it is better suited for our purposes. A ProSave model consists of atomic or composite components connected through ports (divided into input and output ports), and connections. Ports and connections represent data flow between components. Definition 3 (Component Syntax). A component C is a tuple I , O , P , in, out , f , e, where – I, O, and P are mutually disjoint sets of input, output, and private variables respectively, – in : I → Bool is a boolean expression over input variables I that triggers the execution of the component, – out : O → Bool is a boolean expression over output variables O that indicates that the component has completed its execution, – f : I × P → P × O is a function that maps input and private values to the private and output values, and – e ∈ N is a constant representing the execution time of the component. We denote by X = I ∪O ∪P the set of all variables with size |X | = |I |+|O |+|P |. We will further use C .n to denote the elements of a component, hence e.g., C .I denotes the input variables of component C . We now introduce the formal syntax of the ProSave model. Definition 4 (ProSave Syntax). A ProSave design model is a tuple C, →, where – C = {C0 , ..., Cn } is a set of components, – → ⊆ C × C is a set of component connections, such that output variables Ci .O may be connected to input variables Cj .I We will write Ci .Om → Cj .In to represent the connection from output variable m of component Ci to input variable n of component Cj . A ProSave system is typically driven by a periodic clock which periodically generates a control (or trigger) signal. A clock component is defined as follows: Definition 5 (Clock Component). A component C = I , O , P , in, out , f , e is a clock component with period p iff | I |=| O |= 1, e = p, and C .O → C .I . Fig. 2 shows a ProSave design model consisting of seven components (depicted as boxes) interconnected by data and control flow connections (depicted as solid arrows indicating the flow direction). Component SystemClock is a clock component with period 40. The other six components have execution time 10. Their internal behavior may be specified using a formalism based on statecharts [14] or timed automata [2], which we are not explicitly concerned with in this paper.
J. Suryadevara et al.
trig
Sensor (se) 10
FBfo
tfo
sl, sr
Controller (co) 10
sl
,s
r,
SystemClock (sc) 40
Follow (fo) 10 fo
60
sl,
sr ,
sl, sr,
tu
Turn (tu)
ttu 10 FBtu
Actuator (ac) 10
tfi
fi
Find (fi)
10 FBfi
Fig. 2. Schematic view of a ProSave design model of the autonomous truck
A component starts execution when it receives control input. It then reads its input and proceeds with internal computation. When the internal execution is completed, data and control output is generated for other components. We will now give the formal semantics of the subset of ProSave used in this paper. For the semantics of the full ProCom language, we refer the reader to [15]. For a ProSave model consisting of components C0 , ..., Cn , we use V to denote the set of all variables in a model, i.e., V = X0 ∪ ... ∪ Xn . The semantics is defined using valuations α mapping each variable in V to values in the type (or domain)2 of V , and vectors β¯ of βi ∈ {0, ..., ei , ⊥} representing the remaining execution time of all components Ci . We use fi (α) to denote the valuation α in which α (xi ) for each xi ∈ Pi ∪ Oi is the value obtained by applying the function Ci .f in the valuation α, and α (x ) = α(x ) for all other variables x . To update the execution time vector β¯ ¯ i := n] to denote the β¯ in which β = n and β = βj for all j
we use β[β = i, i j and we write β¯ n to denote the β¯ in which βi := βi − n for all βi ≥ n. Definition 6 (ProSave Semantics). The semantics of a ProSave design model {C0 , ..., Cn }, → is defined as a transition system Σ, σ0 , T where ¯ – Σ is a set of states of the form of a pair α, β, – σ0 ∈ Σ is the initial state α0 , β¯0 which is such that α0 |= Ci .in for all clock ¯ components Ci and α0 |= ¬Cj .in for all other component Cj , and β¯0 = ⊥, – T ⊆ Σ × {CDi , CSi , TP } × Σ is a set of transitions such that the following conditions hold: CSi ¯ i := ¯ − • (component start) α, β −→ α , β¯ if (Ci .in ∧ (βi = ⊥)), β¯ = β[β ei ], and for all i
= j : βj
= 0, CDi ¯ → α , β¯ if βi = 0, α = fi (α), and β¯ = • (component done) α, β −−− ¯ β[βi := ⊥], 2
We assume all variables in V are of type Boolean or finite domained integers.
Bridging the Semantic Gap between Abstract Models of Embedded Systems
61
TP ¯ − • (time passing) α, β −→ α , β¯ if for all i : ¬(Ci .in ∧ (βi = ⊥)) and ¯ ¯ βi
= 0, β = β 1, and (α = α).
where CSi ∈ {CS0 , ..., CSn } and CDi ∈ {CD0 , ..., CDn }.
Intuitively, in the initial state only the clock components are triggered and the remaining execution time of all components are undefined. The “component start” rule describes how components are started. A component Ci may start its execution provided that all completed components have written their output. When Ci starts, its execution time is set to ei . The “component done” rule describes that when a component Ci completes its execution, its output values are generated and mapped to the input values of the connected components according to connection relation →, and its remaining execution time is updated to ⊥ to reflect that it is inactive. Rule “time passing” describes how time progresses in the design model. As time progresses the remaining execution time βi of each active component Ci is decremented by 1. A trajectory of a design model is an infinite sequence γ0
γ1
π = α0 , β0 −→ α1 , β1 −→ α2 , β2 ... a
i αi+1 , βi+1 ∈ T is a where α0 , β0 ∈ σ0 is an initial state, and αi , βi −→ transition such that γi ∈ {CDi , CSi , TP } for all i ∈ N.
3
Case Study: Autonomous Truck
The autonomous truck is part of a demonstrator project conducted at the Progress research centre3 . The truck moves along a specified path (as illustrated in Fig. 3), according to a specified application behavior. In this section we give an overview of the truck application followed by a specification, and a design model, described in the modeling languages introduced in the previous section.
Fig. 3. Path of the truck movement
3
For more information about Progress, see http://www.mrtc.mdh.se/progress/
62
J. Suryadevara et al.
Fig. 4. The design model of the autonomous truck in SaveIDE
We will study a simplified version of the case study, in which the truck should simply follow a line. When it reaches the end of the line, it should try to find back to the line, follow the line again in the opposite direction, and repeat its behavior. The truck will have the following operational modes (see also Fig. 1): – Follow: in which the truck follows the line (the thick line of Fig. 3) using light sensors. When the end of the line is detected, it changes to Turn mode. – Turn: the truck turns right for a specified time duration, and then changes to Find mode. – Find: the truck searches for the line. When it is found, the truck returns to Follow mode. A specification model of the case study is given in Fig. 1. It starts in location Follow. The end of the line is modeled using external event e o l(). In location Turn, it turns for four seconds, and then proceeds to location Find when the timer expires. The external event line found() models that the line is found and control switches back to the initial location Follow. The schematic view of a ProSave deign model of the case study is given in Fig. 2. The original model (as shown in Fig. 4) was developed using SaveIDE [12], an integrated development environment supporting the subset of ProSave
Bridging the Semantic Gap between Abstract Models of Embedded Systems
63
used in this paper. As shown in Fig. 2, the design model consists of components SystemClock (a periodic clock), Sensor, Controller, Follow, Turn, Find and Actuator. Component SystemClock triggers the complete model periodically through the component Sensor which reads the light sensors of the truck. The sensor values (left, right) are communicated through the data ports sl and sr. Note, a connection between two components as shown in Fig. 2, denotes a collection of independent port connections between corresponding data or trigger ports of the components. Component Controller acts as a control switch for triggering the components Follow, Turn, and Find selectively , through control ports fo, tu, fi respectively, which contain the functionality of the corresponding modes of the truck behavior. The completion of execution of each operational mode (the corresponding component) is indicated by data (port) values FBfo , FBtu , and FBfi respectively. Component Actuator, triggered by control port tfo, ttu, or tfi, actuates the corresponding hardware to cause the physical activity of the truck movement. As discussed previously, the periodicity of the SystemClock is 40 time units and the execution times of each of other components is 10 time units.
4
Methodology Description
In Section 2, we have described the syntax and semantics of two models used in the development of embedded system software: the event-based model of UML statemachines, and the time-triggered and data-flow oriented model of ProCom. These are examples of modeling languages that are aimed at providing different views of embedded systems, used in different stages or at different abstraction levels during system development. The common use of different models creates a need for comparing descriptions of systems made in different modeling languages. In this section, we propose a method for comparing event-based and timetriggered models of embedded systems. The method will be described and illustrated on UML statemachines and ProCom models of the autonomous truck case study described in the previous section. Constructing a semantic bridge between the two models requires a series of steps that need to be systematically applied. Our methodology for bridging the gap between the paradigms consists of the following five steps: (i) given a specification trajectory, generate a corresponding design trajectory by e.g, simulating the model; (ii) simplify the specification trajectory (can be omitted); (iii) simplify the design trajectory; (iv ) transform the design trajectory into one comparable to the event-based specification trajectory; (v ) compare the reduced specification and design trajectories. To support above described steps (ii) to (iv ) of the method we will present in Sections 4.1 to 4.3 a number of inference rules for simplifying specification and design trajectories, and for transforming between the two. In the latter transformation step, we need to take two crucial steps. One is to relate events in the UML statemachine model to the data-flow of the ProCom model. This is done by mapping events observed in the specification trajectories to predicates
64
J. Suryadevara et al.
over the data variables used in the design model. We expect that a designer will easily be able to provide this mapping based on his insights and knowledge in the models. For the autonomous truck system, we can assume a mapping given in Table 1 in section 4.2. A second important step in relating two models of embedded systems regards the different time scales that may be used. We take a rather straightforward approach and assume a δ, as defined in section 4.3, for characterizing the sampling period in design models, in comparison to the time base used in the specification model. 4.1
Specification Simplification Inference Rules
In the following rules, we denote by si ∈ S , i ∈ N, the states of an arbitrary specification model trajectory. Skip time rule. This rule states that a sequence of tick transitions corresponding to a location without an associated timeout can be ignored. tick
tick
tick
si −−→ si −−→ . . . si
−−→ si
(skip)
By applying this rule to the original specification trajectory of the Autonomous truck (omitted due to space limitations), we get the simplified trajectory shown in Fig. 5.(a). Time passing rule. The intuition behind this rule is that one can collapse a sequence of tick transitions corresponding to a timeout location in the specification model, into a single transition that collects all the ticks. Consequently, the intermediate states generated by the individual ticks become hidden. tick
tick
tick
si −−→ si+1 −−→ . . . −−→ si+n n.tick
si −−−−→ si+n
(n tick)
To show the rule at work, we have used it to reduce the sequence of tick transitions (s1 to s5 ) displayed in Fig. 5.(a), to the corresponding sequence in Fig. 5.(b). Timeout start rule. Here, we introduce the virtual event tm−start needed to distinguish the transition leading to the corresponding timeout annotated location, from the one fired when the timeout countdown starts. Although not a simplification rule by itself, its usefulness is shown in the rules skip and n TP, presented later. event−label
si −−−−−−−→ si+1
m = value
event−label
m
= ε ∧ m
=0
tm−start
si −−−−−−−→ si+1 − −−−−− → si+2
(tm start)
Bridging the Semantic Gap between Abstract Models of Embedded Systems
65
s0
e_o_l
s1 tick
s0
s2
e_o_l
tick
s3 tick
s4
s1 n_tick
tm
tm
s6
s1 tm(4)
s6
line_found
line_found
s7
s7
(b)
(c)
tm
s6
e_o_l
s5
tick
s5
s0
4. tick
line_found
s7 (a)
Fig. 5. Examples of specification trajectories simplifications of the autonomous truck
In the above rule, value ∈ {0, ε}. In case value = 0, that is, m = 0, it follows that event− label = tm; on the other hand, if value = ε, that is, m = , then event− label = a. Timeout rule. A sequence of n-tick transitions beginning at a location having timeout n that is then followed by a timeout transition can be reduced to a single transition denoted by tm(n), as shown below: n.tick
tm
si −−−−→ si+1 −−→ si+2 tm(n)
(tm)
si −−−−→ si+2 After applying the timeout rule, the sequence of the 4-tick transitions (s1 to s5 ) followed by the tm transition (s5 to s6 ), depicted in Fig. 5.(b), is reduced to transition (s1 to s6 ), as in Fig. 5.(c). 4.2
Design Simplification Inference Rules
As already mentioned, in order to be able to relate the specification and design models formally, we require the detailed mapping of the external and timeout events of the specification model onto predicates over data values of the corresponding design model. In addition to the observable events, such mapping should also include the virtual timeout start event, tm start. We assume that such a mapping is provided by the ProSave designer, as he/she “implements” the specification model. For the current design model of the autonomous truck, one such mapping is given in Table 1.
66
J. Suryadevara et al. Table 1. Events and corresponding predicates of the autonomous truck models Events
Predicates
e 0l sl ∧ sr ∧ FBfo line found (sl ∨ sr ) ∧ FBfi tm(timeout event) FBtu tm start ttu
>
:
α sc.in i.e., sc.in holds in α , and β is the initial valuation of β
σ1 <-, β =2>
:
α sc.in and also no other predicates hold in α
:
α =α and ∀x∈β . x≠ β
:
α =α and ∀x∈β . x≠ β β [x]=β [x] and β [β ]=0
:
α sc.in Λ sr.in and β = β
σ0 <sc.in, β
0
0
0
0
CSsc sc
1
1
TP
σ2 <-, β =1> sc
2
1
β [x]=β [x] and β [β ]=1
sc
2
1
2
sc
TP
σ3 <-, β =0> sc
3
2
sc
2
1
2
sc
CDsc
σ4 <sc.in,sr.in, ->
4
4
3
CSsc
.. .
Fig. 6. Interpretation of example design trajectory notation w.r.t. Definition 6
Below, we denote by σi ∈ Σ, i ∈ N, the states of an arbitrary design model trajectory. In Fig. 6, we give an excerpt of a design trajectory of the autonomous truck, and, on the right-hand side of the figure, we explain the used notation in terms of Definition 6 of Section 2. Skip time rule. This rule states that a sequence of TP-transitions from states that do not satisfy the predicate corresponding to the virtual event tm start can be ignored. Such transitions correspond to time passing in the design trajectory, which are of no interest, that is, not related to observable timeout events. TP
σi −−→ σi+1
σi Predtm σi
start
(Skip)
We apply the skip time rule on a design trajectory of the autonomous truck (see Fig. 7), and, as a result, we simplify the trajectory by reducing states σ1 , σ2 , σ3 , and σ4 , to state σ1 only. The complete trajectory is given in the Appendix. Hide CS rule. By this rule, a CS-transition, hence the target state, can always be ignored. CS
i σi −−→ σi+1 σi
(hide CS)
Assuming a design trajectory of our case-study, the application of the above rule on this trajectory is shown in Fig. 8.
Bridging the Semantic Gap between Abstract Models of Embedded Systems
σ0 <sc.in, β0 >
CSs c
σ1 <-, βsc=4>
TP
σ2 <-, βsc=3>
TP
σ3 <-, βsc=2>
TP
σ4 <-, βsc=1>
TP
σ5 <-, βse=0>
CD sc
σ6 <sc.in,sr.in, ->
67
...
CSsc
skip
σ0 <sc.in, β0 >
σ1 <-, βsc=4>
CSsc
σ6 <sc.in,sr.in, ->
CD s c
CSs c
...
Fig. 7. Application of skip rule on a design trajectory of the autonomous truck model
a)
σ41 <-, βsc=1,βtu=0>
CDtu
σ44 <-, βsc=0,βac=0>
CDac
σ47 <-, βsc=4>
b)
σ45 <-, β sc=0>
σ48 <-, β sr=1>
CSs r
σ50
CSc t
σ53
CS tu
σ56
σ42
CSac CDac
σ43 <-, βsc=1,βac=1> σ46 <sc.in,sr.in, ->
σ49 <-, β sc=3,βsr=0>
TP
σ51 <-, β sc=3,β ct=1>
TP
σ54 <-, βsc=2,βtu=1>
TP
TP
CSsc
CD sr
σ52 <-, β sc=2,βct=0> σ55 <-, βsc=1,β tu=0>
CDc t CDtu
...
TP
hide_CS
σ41 <-, βsc=1,β tu=0>
CD tu
σ45 <-, βsc=0>
σ46 <sc.in,sr.in, ->
CD ac
σ50
σ56
CDtu
TP
σ44 <-, βsc=0,βac =0>
σ49 <-, βsc=3,βsr=0>
TP
σ52 <-, βsc=2,βct=0>
TP
σ55 <-, βsc=1,βtu=0>
σ42
CDct TP
CDac
CD sr
σ53
TP
...
hide_CD
σ41 <-, βsc=1,βtu=0> c)
σ50 σ56
d)
CDtu
σ53
TP
TP
TP
σ46 <sc.in,sr.in, ->
σ55 <-, βsc=1,βtu=0>
TP CD tu
...
TP
σ41 <-, βsc=1,βtu=0>
σ42
n_TP CD tu
σ42
4. TP
σ56
TP
...
Fig. 8. (a) a partial design trajectory of the autonomous truck, and (b) to (d) corresponding reduced trajectories after application of the inference rules of Section 4.2
Hide CD rule. This rule stipulates that a CD-transition and the corresponding source state can be ignored if the target state does not satisfy any event occurrence predicate. CD
i σi −−− → σi+1
∀a ∈ A · σi Preda σi+1
(hide CD)
An example application of the above rule is given in Fig. 8. Time passing rule. A sequence of TP transitions starting in a state satisfying the predicate corresponding to tm start, and ending in a state where the
68
J. Suryadevara et al.
corresponding timeout occurs, can be collected into a single transition, while the intermediate states are ignored. TP
TP
CDj
σi −−→ σi+1 ... −−→ σi+n −−−→ σi+n+1
σi |= Predtm
start
σi+n+1 |= Predtm
n.TP
σi −−−→ σi+n+1 (n TP) We have applied the above rule on a design trajectory of our Autonomous Truck, in Fig. 8. The rule works on the states σ42 , σ46 , σ50 , σ53 , σ55 and σ56 . Precedence of inference rules. In order to get the correct simplified design trajectory, we assume the following precedence rule when applying the above inference rules over design trajectories (rule hide CS binds the strongest): hide CS precedes hide CD precedes n TP precedes Skip 4.3
Rules for Transforming the Design Model Trajectories
The following rules let one obtain design trajectories that are comparable to the event-based specification model trajectories. The first rule focuses on relating the time scales in both models; in order to achieve the goal, we assume a fixed quanta of time (number of time units), called δ, which can be viewed as the minimum amount of time guaranteed to be free of events. Then, this smallest amount of time becomes the basic time-unit that all time-related elements in both trajectories can be expressed by. TimeOut Rule. We assume that a TP-transition “consumes” δ time units, the time duration associated with a tick event is (m* δ)(m ∈ N), and an ‘n’ time units timeout in a specification trajectory, tm(n), equals (n* tick). Then, it follows that an n.m.TP transition in the design trajectory is equivalent to the ‘n’ timeout event, tm(n): ∃ σk , σk +1
n.m.TP
. σi −−−−−→ σi+1 tm(n)
∃ sk , sk +1
(TO)
. sk −−−−→ sk +1
EventOccur Rule. An event occurrence in a specification trajectory corresponds to a CD-transition in the design trajectory, such that the predicate associated to the event holds in the target state of the design trajectory. ∃
σi , σi+1
CDj
. σi −−−→ σi+1
∃ sk , sk +1
a
σi+1 |= Preda
. sk − → sk +1
(EO)
Next, we apply the above rules on a (simplified) design trajectory of our example, in order to obtain a trajectory comparable to the corresponding specification trajectory.
Bridging the Semantic Gap between Abstract Models of Embedded Systems
4.4
69
Applying the Methodology
Here, we show our methods at work, on the Autonomous Truck case study, presented in Section 3. We do this by transforming a trajectory of the design model (Fig. 2), which we present in the Appendix, into one that is comparable to the corresponding specification model (Fig. 1) trajectory. First, the design trajectory is simplified by applying the inference rules introduced in section 4.1. Similarly, a trajectory of the specification model is then simplified to a minimal form by applying inference rules in 4.2. Both simplified trajectories are shown in Fig. 9. Next, we relate these trajectories by using the inference rules of transformation (section 4.3), as follows: σ0 <sc.in, β0 >
s 0
CDfo
EO
σ30 <sl, sr, FBfo, βsc=1>
s1
4.TP
σ56 CDfi
σ82 <sl, FBfi , βsc=1>
e_o_l
tm(4)
tm(4)
s 5 EO
line_found
s 6
...
...
(a)
(b)
Fig. 9. Comparison of completely reduced trajectories of (a) the design model, and (b) the specification model, of the autonomous truck
– by EO rule, the CDfo -transition to state σ30 corresponds to the occurrence of event e o l , since the (e o l ) predicate, that is, FBfo ∧ sl ∧ sr , holds in the target state σ30 . Further, σ30 corresponds to the completion of the Follow mode of the truck behavior, as FBfo holds (by design). – similarly, by EO rule, the CDfi -transition to state σ82 corresponds to the occurrence of event line found , since Predline found , that is, FBfi ∧ sl , holds in the target state σ82 . Further, σ82 corresponds to the completion of the Find mode of the truck behavior, as FBfi holds (by design). – by TO rule, the timeout event, tm(4), between specification states s2 and s6 corresponds to the 4.TP -transition between design states σ30 and σ56 that satisfy Predtm start , and Predtm , respectively. Further, σ56 corresponds to the completion of the Turn mode, as FBtu holds (by design). By applying the rules on the truck example, we have shown that, at least with respect to this example, it is possible to transform and compare a simplified design model trajectory of the Autonomous Truck with a simplified specification model trajectory. The transformation correlates also the time scales in both models. In this particular case, we have reduced the design model trajectory to an event-based trajectory identical to the specification one.
70
J. Suryadevara et al.
The above steps are necessary in proving the correctness of design with respect to specification, however they are not sufficient. To get closure, one has to first consider all possible design behaviors for transformation, and then possibly apply refinement techniques to prove that the design does implement the functional and timing requirements represented by the specification model (see Section 6).
5
Related Work
The problem of relating design to specification models is a topic with a growing interest in the research community. For synthesizing executable programs from timed models, a timed automata [2] based semantic framework, relying on non-instant observability of events is proposed [6]. Time-triggered automata (TTA) - a sub class of timed automata (TA) - are used to model finite state implementations of a controller that services the request patterns specified by a TA. This technique enables deciding whether a TTA correctly implements a TA specification. In comparison, although ProCom oriented, our methodology can be applied within a generic component-based framework, and is not being tied to any particular formal verification framework either. Sifakis et al. propose a methodology for relating the abstractions of both real-time application software and corresponding implementation [13]. The related formal modeling framework integrates event-driven, and time triggered paradigms by defining untiming functions. Problems of correctness, timing analysis, and synthesis are considered in the methodology. In contrast to our approach, this one does not address the intermediate design layer commonly used in system development. In recent years, component and architecture based developments have been recognized as a viable way of building software systems [5]. Plasil and Visnovsky describe a formal framework based on behavior protocols, in order to formally specify the interplay between components [10]. This allows for formal reasoning about the correctness of the specification refinement and about the correctness of an implementation, in terms of the specification. Further, the framework is validated in the SOFA component model environment [4]. While the approach provides much needed formal correctness in component-based development, it does not address timing issues and vertical layers of abstractions in real-time system development. UML has emerged as an industrial standard notation in system development and provides various sub-languages namely statemachines, sequence diagrams, etc [11]. For specification and design of real-time systems, a sub-language called UML/MARTE has been proposed [9]. In [7], the expressiveness of MARTE for event-triggered, and time-triggered communication is described. MARTE-based approaches facilitate various analytical methods for analysis, e.g., schedulability, system performance analysis; however, it falls short in providing formal support for comparing models at different abstraction levels. Egyed, A. et al. [8] develop a methodology to mainly bridge the information gap created by heterogeneous models across the software life-cycle by transforming architecture description into (high-level) UML designs. The latter are then
Bridging the Semantic Gap between Abstract Models of Embedded Systems
71
further refined into lower level designs. In contrast to our approach, their work does not provide details on the behavioral transformations. Indeed, a formal approach for establishing the semantic links between different terminologies and concepts across an architectural and a number of design models is not sufficiently addressed during the transformation.
6
Conclusions and Future Work
In this paper, we have presented a formal approach for relating system models used in different design stages of embedded systems. For the early specification phases, we chose the UML statemachine language in which system behaviors are described in terms of abstract states, event triggered state changes, and timeouts relating to the external system and timing behavior. For the later design stages, we use the ProCom component design model in which systems are specified using data-flow connectors and time-triggered component behaviors closer to the timing granularity and behavior exhibited on the target platform. As a main result, we have described a formal way of comparing behavioral models of a system modeled in the two different languages. The solution is based on a set of inference rules that can be applied to gradually transform trajectories of a ProCom design model into a trajectory of a UML specification model. This enables a designer to make sure that a component-based and time-triggered ProCom design model implements the behavior of a more abstract and eventtriggered UML specification of the same system. Our initial experiences from applying the proposed technique to a truck control system, indicates that the design model trajectories can often be manually transformed into trajectories of the specification model. However, as this is not the case in general, we plan as future work to apply simulation relation checking to the specification trajectories, to prove (or disprove) conformance between non-identical trajectories. We will apply proof assistant tools to support these techniques.
References ˚kerholm, M., Carlson, J., Fredriksson, J., Hansson, H., H˚ 1. A akansson, J., M¨ oller, A., Pettersson, P., Tivoli, M.: The SAVE approach to component-based development of vehicular systems. Journal of Systems and Software 80(5), 655–667 (2007) 2. Alur, R., Dill, D.L.: A theory of timed automata. Theoretical Computer Science 126(2), 183–235 (1994) 3. Bures, T., Carlson, J., Crnkovic, I., Sentilles, S., Vulgarakis, A.: ProCom - the progress component model reference manual, version 1.0. Technical Report, ISSN 1404-3041 ISRN MDH-MRTC-230/2008-1-SE, M¨ alardalen University (June 2008) 4. Bures, T., Hnetynka, P., Plasil, F.: Sofa 2.0: Balancing advanced features in a hierarchical component model. In: SERA 2006: Proceedings of the Fourth International Conference on Software Engineering Research, Management and Applications, Washington, DC, USA, pp. 40–48. IEEE Computer Society, Los Alamitos (2006)
72
J. Suryadevara et al.
5. Crnkovic, I., Larssom, M.: Challenges of component based development. J. Syst. Softw. 61(3), 201–212 (2002) 6. Krˇca ´l, P., Mokrushin, L., Thiagarajan, P.S., Yi, W.: Timed vs time triggered automata. In: Gardner, P., Yoshida, N. (eds.) CONCUR 2004. LNCS, vol. 3170, pp. 340–354. Springer, Heidelberg (2004) 7. Mallet, F., de Simone, R., Rioux, L.: Event triggered vs. time-triggered communications with UML Marte. In: FDL, pp. 154–159 (2008) 8. Medvidovic, N., Gr¨ unbacher, P., Egyed, A., Boehm, B.W.: Bridging models across the software lifecycle. J. Syst. Softw. 68(3), 199–215 (2003) 9. OMG. Unified modeling language (uml) profile for modeling and analysis of realtime and embedded systems (marte). In: Document ptc/07-08-04. OMG (2007) 10. Plasil, F., Visnovsky, S.: Behavior protocols for software components. IEEE Trans. Softw. Eng. 28(11), 1056–1076 (2002) 11. Rumbaugh, J., Jacobson, I., Booch, G.: Unified Modeling Language Reference Manual, 2nd edn. Pearson Higher Education, London (2004) 12. Sentilles, S., Pettersson, A., Nystr¨ om, D., Nolte, T., Pettersson, P., Crnkovic, I.: Save-IDE - a tool for design, analysis and implementation of component-based embedded systems. In: Proceedings of the Research Demo Track of the 31st International Conference on Software Engineering (ICSE) (May 2009) 13. Sifakis, J., Tripakis, S., Yovine, S.: Building models of real-time systems from application software. In: Proceedings of the IEEE Special issue on modeling and design of embedded, pp. 100–111. IEEE, Los Alamitos (2003) 14. Slutej, D., H˚ akansson, J., Suryadevara, J., Seceleanu, C., Pettersson, P.: Analyzing a pattern-based model of a real-time turntable system. In: Happe, B.Z.J. (ed.) ESOP 2009. Electronic Notes in Theoretical Computer Science (ENTCS), York, UK, March 2009 vol. 253, pp. 161–178. Elsevier, Amsterdam (September 2009) 15. Vulgarakis, A., Suryadevara, J., Carlson, J., Seceleanu, C., Pettersson, P.: Formal semantics of the procom real-time component model. In: 35th Euromicro Conference on Software Engineering and Advanced Applications (SEAA) (August 2009)
Bridging the Semantic Gap between Abstract Models of Embedded Systems
APPENDIX
σ0 <sc.in, β0 > CSsc
σ1 <-, βsc=4> 4.TP
σ5 <-, βse=0> CDsc
σ6 <sc.in,sr.in, -> CSs c,CSs r
σ8 <-, βsc=4,βsr=1> TP
σ9 <-, βsc=3,βsr=0> CDs r
σ10 CS c t
σ11 <-, βsc=3,βct=1> TP
σ12 <-, βsc=2,βct=0> CDc t
σ13 CSfo
σ14 <-, βsc=2,βfo=1> TP
σ15 <-, βsc=1,βfo=0> CDfo
σ16 CSac
σ17 <-, βsc=1,βac=1> TP
σ18 <-, βsc=0,βac=0> CDs c,CDac
σ20 <sc.in,sr.in, -> CSs c,CSs r
σ22 <-, βsc=4,βsr=1> TP
σ23 <-, βsc=3,βsr=0> CDs r
σ24 CSc t
σ25 <-, βsc=3,βct=1> TP
σ26 <-, βsc =2,βct=0> CDc t
σ27 CSfo
σ28 <-, βsc=2,βfo=1> TP
σ29 <-, βsc=1,βfo=0> CDfo
σ30 <sl, sr, FBfo, βsc=1> TP
σ31 <-, βsc=0> CDs c
σ32 <sc.in,sr.in, -> CSs c,CSsr
σ34 <-, βsc=4,βsr=1> TP
σ35 <-, βsc=3,βsr=0> CDs r
σ36 CSc t
σ37 <-, βsc=3,βct=1> TP
σ38 <-, βsc=2,βct=0> CDc t
σ49 <-, βsc=3,βsr=0> CDs r
σ50 CSc t
σ51 <-, βsc=3,βct=1> TP
σ52 <-, βsc=2,βct=0> CDc t
σ53 CStu
σ54 <-, βsc=2,βtu=1> TP
σ55 <-, βsc=1,βtu=0> CDtu
σ56 TP
σ57 <-, βsc=0> CDs c
σ58 <sc.in,sr.in, -> CSs c, CSsr
σ60 <-, βsc=4,βsr=1> TP
σ61 <-, βsc=3,βsr=0> CD sr
σ39
σ62
σ40 <-, βsc=2,βtu=1>
σ63 <-, βsc=3,βct=1>
CStu
TP
σ41 <-, βsc=1,βtu=0> CDtu
σ42 CSac
σ43 <-, βsc=1,βac=1> TP
σ44 <-, βsc=0,βac=0> CDs c,CDac
σ46 <sc.in,sr.in, -> CSs c,CSs r
σ48 <-, βsc=4,βsr=1> TP
CSct
TP
σ64 <-, βsc=2,βct=0> CD ct
σ65 CSfi
σ66 <-, βsc=2,βfi=1> TP
σ67 <-, βsc=1,βfi=0> CDfi
σ68 CSac
σ69 <-, βsc=1,βac=1> TP
σ70 <-, βsc=0,βac=0> CDs c,CDac
σ72 <sc.in,sr.in, -> CSs c,CSs r
σ74 <-, βsc =4,β sr=1> TP
σ75 <-, βsc =3,β sr=0> CDs r
σ76 CSc t
σ77 <-, βsc=3,βct=1> TP
σ78 <-, βsc=2,βct=0> CDc t
σ79 CS fi
σ80 <-, βsc=2,βfi=1> TP
σ81 <-, βsc=1,βfi=0> CDfi
σ82 <sl,FBfi, βsc=1> TP
σ83 <-, βsc=0> CDs c
σ84 <sc.in,sr.in, -> CSs c ,CSs r
σ86 <-, βsc=4,βsr=1> TP
σ88 <-, βsc=3,βsr=0> CDs r
σ89 CSc t
σ90 <-, βsc=3,βct=1> TP
σ91 <-, βsc=2,βct=0> CDc t
σ92 CSfo
σ93 <-, βsc=2,βfo=1> TP
Fig. 10. An execution trajectory of the design model of the autonomous truck
73
Reliable Dynamic Reconfigurations in a Reflective Component Model Marc L´eger1, Thomas Ledoux1,2 , and Thierry Coupaye3 1
Ascola (EMN-INRIA, LINA) Ecole des Mines de Nantes, France [email protected] 2 INRIA, France [email protected] 3 Orange Labs, France [email protected]
Abstract. Software engineering must cope with a more and more increasing need for evolutivity of software systems in order to make their administration easier. However, evolution and especially dynamic evolution in a system must not be done at the expense of its reliability, that is to say its ability to deliver the expected functionalities. Actually runtime modifications in a given system may lead to inconsistent states and so it can have an impact on its reliability. The aim of this paper is to guarantee reliability of dynamic reconfigurations used to make component-based systems evolve at runtime while preserving their availability. We propose a definition of consistency for configurations and reconfigurations in Fractal component architectures with a model based on integrity constraints like for example structural invariants. Reliability of reconfigurations is ensured thanks to a transactional approach which allows us both to deal with error recovery and to manage distributed and concurrent reconfigurations in Fractal applications. Keywords: component-based architectures, dynamic reconfigurations, reliability, transactions, integrity constraints.
1
Introduction
Software evolution [14] is recognized as an inevitable nature of a complex software system. Software systems should evolved to keep their functionalities along with the environment changes. In practice, there are many motivations to modify the system’s architecture at runtime without stopping the whole system in order to preserve its continuity of service. Maintenance (e.g., security flaws, functional bugs), optimisation (e.g., code source, resources allocation), or adaptations to execution context changes (i.e., context awareness) are examples where it should be necessary to reconfigure dynamically only a sub-part of the system’s architecture while it remains available. The key role of self-adaptive systems in enabling the development of future software systems has been now recognized [5]. L. Grunske, R. Reussner, and F. Plasil (Eds.): CBSE 2010, LNCS 6092, pp. 74–92, 2010. c Springer-Verlag Berlin Heidelberg 2010
Reliable Dynamic Reconfigurations in a Reflective Component Model
75
Component-Based Software Engineering (CBSE) is a powerful approach to build complex software [24] because it proposes several key features such as the explicit representation of dependencies between components to assess the validity of architectures and to modify/reuse building blocks [22]. In particular, some of them (e.g., Fractal [4], OpenCom [6]) provide a good base to modify systems at runtime since they support dynamic reconfiguration and reflective features. However, reliability (a key concept of dependability [2]) can become a crucial problem in systems subject to dynamic reconfigurations. Indeed, dynamic reconfigurations may be invalid since they leave a system in an inconsistent state, i.e. no more available or unusable from a functional point of view. We can distinguish two kinds of errors. The first one deals with the violation of the system integrity. Component models should define what a consistent system is, for instance in terms of structural assemblies, and dynamic reconfigurations should not break this consistency (e.g., a hierarchical component architecture is cycle-free). In addition, some reconfigurations must ensure the conformity of specific architectural invariants (e.g., it could be forbidden to share a component between several parents). The second problem deals with hardware crashes: a fault can happen during the runtime reconfiguration letting the system unsafe. Our proposal aims to ensure the reliability of dynamic reconfigurations in CBSE and can be applied to concurrent, distributed and non-anticipated reconfigurations. This approach is relevant for all component models provided that they propose a runtime support for dynamic reconfigurations, which is the case with reflective models. The contribution is divided into two parts: (i) a definition of consistency for components (re)configuration; (ii) a transactional approach for dynamic reconfigurations. First, configuration consistency relies on integrity constraints. An integrity constraint is a predicate on assemblies of architectural elements and component state. We have to check invariants to validate a configuration. Then, a reconfiguration, i.e. a transition between two configurations, can be certified only if the resulting configuration is consistent: if all pre/post conditions on transitions and integrity constraints on the new configuration are satisfied. We define integrity constraints with first-order logic and we use Alloy [11], a language allowing programmers to express complex constraints and amenable to a fully automatic semantic analysis, to check the global consistency of the constraints. In order to guarantee reliable reconfigurations, we propose a model and a runtime support for fault tolerance. We adopted a transactional approach where a reconfiguration is seen as a transaction supporting fault recovery, architecture consistency and concurrency of distributed reconfigurations. In practice, we experiment with the Fractal model [4] because of its native support of dynamic architectures. We intend to reify the concept of a Fractal configuration dealing with integrity constraints to reason more easily on reconfigurations. Constraints violation will be detected and managed by our transactional support. This paper is organized as follows. Section 2 defines a consistency model for Fractal architectures to specify the semantics of reconfiguration. Section 3
76
M. L´eger, T. Ledoux, and T. Coupaye
describes how transactions combined with integrity constraints can be a support for reliable reconfigurations. Section 4 proposes a performance evaluation of our contribution and gives a use case in the context of system self-repair [12]. Finally, section 5 presents some related work before concluding in section 6.
2
Modelling Configurations and Reconfigurations
In this section, we first present a short overview of the Fractal component model. We propose then a model of Fractal configurations with a graph-based representation and integrity constraints. We explain also how to check the consistency of Fractal configurations. The model is finally extended in order to specify dynamic architectures and reconfiguration operations. 2.1
An Overview of the Fractal Component Model
We chose Fractal [4] because it provides a good base to build adaptable systems. Fractal is a hierarchical component model with sharing and reflectivity. It is based on classic concepts of component (as a runtime entity), interface (an interaction point between components expressing provided and required services) and binding (a communication channel between component interfaces). A component consists of a membrane which can show and control a causally connected representation of its encapsulated content. The content is either directly an implementation in case of a primitive component or sub-components for composite components. Binding semantics is not fixed and is extensible. Communication semantics for primitive bindings is by default synchronous method invocation between component interfaces in a same address space. Composite bindings as component assemblies can be used to implement more complex communications such as distributed communications. 2.2
A Model of Component Configurations
A component model provides some rules of assembly for software systems with properties for good structuring and modularity [22]. The Fractal model is essentially defined by an informal textual specification and a low-level APIs written with an interface definition language. Then, we propose to define a more formal representation of configurations, i.e component assemblies, in the model. A graph-based representation of Fractal configurations. A Fractal architecture is modelled by a static configuration of architectural elements belonging to the component model. We represent a Fractal configuration as a directed labelled multi-graph A = (E, R) where the set of nodes E represents the architectural elements in the configuration and the set of arcs R is the set of relations between these elements. The Fractal component model is then defined by the triplet F = (FE , FR , FP ) where:
Reliable Dynamic Reconfigurations in a Reflective Component Model
77
– FE is the set of architectural elements belonging to the component model; – FR is the set of possible relations between architectural elements; – FP is the set of integrity constraints as binary predicates π(E, R) which have to be satisfied by every configuration. Consequently, a configuration A is said to conform to the Fractal model M or equivalently to be valid if and only if: E ⊆ FE R ⊆ FR ∀π ∈ FP , π(E, R) Architectural elements and relationships. Architectural elements reified in our model are the core entities of Fractal: components (Component), component interfaces (Interf ace) and component attributes (Attribute)1 . Moreover, some properties are associated to these elements, they are primitive data characterizing the element such as the name or the life-cycle state of a component. For each property, we define read-only accessors as functions which return a property value given an architectural element. For example, the role function for a component interface (role : Interf ace → Role) returns the role (client or server) of this interface. We consider four main binary relations2 between elements: – hasInterf ace, defined on Component × Interf ace, determines if a component owns a given interface. – hasAttribute, defined on Component×Attribute, determines if a component has a given attribute. – hasChild, defined on Component × Component, determines if a component is a direct sub-component of another component. This relation is not symmetric, the first argument must be the supposed parent component. – hasBinding, defined on Interf ace × Interf ace, determines if an interface is directly bound to another interface. This relation is symmetric whatever the role of the interfaces (client or server). These primitive relations can be composed with first order logic and relations defined on a set (e.g., hasChild), can be derived by transitivity, reflexivity and symmetry. For instance, the transitive closure of hasChild, written hasChildtrans , determines if a component is a direct or indirect sub-component of another component in the component hierarchy. Finally, all relations can be reversed, we can for example define a new relation hasP arent which equals to hasChild−1 . To sum up, the sets of architectural elements and primitive relations is as follows: 1 2
Following the Fractal philosophy, bindings are not reified but represented by arcs. We are using afterwards a functional notation for relations, i.e. R(x, y) rather than x R y.
78
M. L´eger, T. Ledoux, and T. Coupaye Element binding relation
+ id : Number + name : String + binding
1
Interface Attribute
+ signature : Type + visibility : Visibility + role : Role + cardinality : Cardinality + contingency : Contingency
+ type : Type + value : Any + attribute 0..*
0..*
0..* + interface
attribute ownership
1
Component
+ component
+ state : State
+ parent 0..*
0..*
1 + component
interface ownership
+ child
component containment
Fig. 1. A Fractal core meta-model
FE = Component ∪ Interf ace ∪ Attribute FR = hasInterf ace ∪ hasAttribute ∪ hasChild ∪ hasBinding The resulting meta-model of Fractal configurations is presented in the figure 1. Every Fractal configuration can then be represented in our model. Integrity constraints on the component model. In addition to architectural elements and their relations, we use integrity constraints to refine the component model specification. Integrity constraints are essentially structural invariants on component architectures so as to compose an architectural style [1]. More precisely, they are binary predicates on the validity of a configuration, i.e. on architectural elements, their properties and relations between them. These constraints are specified using first order logic, set and relational operations on primitive relations already defined. We are first interested in constraints at the component model level, i.e. constraints common to all Fractal configurations. A typical integrity constraint in hierarchical component models is to forbid cycles in the component hierarchy in order to avoid infinite recursion: noCycle(E, R) = ∀c ∈ E, ¬hasChildtrans (c, c)
(1)
We identified the set FP made up fifteen integrity constraints in the Fractal specification to extract a minimalist architectural style which characterizes the Fractal component model. Composite component identification, type compatibility between binding interfaces, role compatibility between binding interfaces, etc. are examples of these integrity constraints. Extending architectural styles with integrity constraints. Once the core of the Fractal component model has been specified in our formalism, we can easily extend its architectural style by adding other integrity constraints. The proposed constraint model is divided into three levels of abstraction:
Reliable Dynamic Reconfigurations in a Reflective Component Model
79
– The model level corresponds to the Fractal component model with generic constraints previously presented. – The profile level is a model extension with some new generic integrity constraints which are common to a set of applications sharing a common architectural style. – The applicative level adds specific constraints on a given application configuration. The profile level must conform to the model level, i.e. profile constraints must not be in contradiction with model constraints, and the applicative level must conform to a chosen profile. Moreover, each level of constraints must be consistent with no contradiction so as to avoid over-constrained configurations. Constraints at the model level are immutable, they can neither be modified nor removed, while profile and applicative constraints can be added or removed at need. This separation in different levels allows us to define in a more flexible way what a consistent configuration is. A profile is composed of a set of integrity constraints which can be added to the model to constrain a family of applications. It could for instance add a constraint to forbid component sharing between composite components: noSharing(E, R) = ∀(c1 , c2 , c3 ) ∈ E 3 , hasChild(c2 , c1 ) ⇒ ¬hasChild(c3 , c1 )
(2)
Applicative constraints are specified in a similar way as other constraints but they only apply to specific elements in given configuration. Every architectural element in a configuration has a unique identifier. To express an applicative constraint to forbid sharing only of a component in an application, we could apply the previous noSharing constraint on this component by designating it by its unique id : noSharing(E, R, id) = ∃c ∈ E, id(c) = id
(3)
∧ ∀(c1 , c2 ) ∈ E , hasChild(c2 , c) ⇒ ¬hasChild(c3 , c) 2
2.3
Consistency of Fractal Configurations
Global approach. Our objective is to be able to check the validity of a configuration of a Fractal component-based system. But, first of all, we have to check the global consistency of all constraints defined in our constraint model: model, profile and applicative levels. We propose to translate constraints described in the first order logic into Alloy [11], a specification language which can be fully automatically analyzed so as to detect inconsistencies between constraints (cf. (1) in Figure 2). Then, once the global consistency of our constraint model has been checked, it is necessary to translate integrity constraints into a language which can be easily integrated in Fractal runtime systems. We chose FPath [8], particularly adapted to specify integrity constraints in Fractal (cf. (2) in Figure 2).
80
M. L´eger, T. Ledoux, and T. Coupaye
Contraints in Alloy
Checking constraints consistency
1
Fractal textual specification + API
Extraction of integrity constraints
Contraints in logic
Translation of contraints
2 Contraints in FPath
Checking constraints in Fractal applications
Fig. 2. Constraint checking process
Checking global consistency. Alloy [11] is a declarative language based on first order logic and relation calculus. Alloy goes with an analyzer relying on a SAT constraint solver to check that specifications are not over-constrained by trying to generate instances of models. The specification of our model of Fractal configurations in Alloy is divided into two main modules: the first one to specify architectural elements and relations, the second one to specify integrity constraints. Indeed, to check the consistency of the model level of constraints, every constraint at the model level is translated into Alloy before being analyzed. The constraint (1) is an invariant and is thus specified by a fact in Alloy: fact noCycle { all a:Configuration | no c: Component { c->c in ^(a.child) } }
Profile constraints and applicative constraints are translated into Alloy in the same way. A profile is specified as a new Alloy module extending the Fractal model specification. The profile constraint noSharing (cf. constraint (2)) is for example specified in Alloy as follows: fact noSharing { all a: Configuration | all c1, c2, c3: Component { (c1->c3 in a.child) => ! (c2->c3 in a.child) } }
Checking constraints in Fractal component-based systems. FPath [8] is a query language for runtime Fractal applications with a XPath-like syntax. It is restricted to the introspection of architectures, navigating inside them to locate elements of interest by their properties or location in the architecture. FPath is particularly adapted to specify integrity constraints in Fractal component-based applications programmed in Java thanks to its integration in this language. The above examples illustrate the implementation of the constraints (1) and (2) as FPath side-effect free functions:
Reliable Dynamic Reconfigurations in a Reflective Component Model
81
function no_cycle(c) { for component : $c::descendant/* { if ($c == $component) { return false(); } return true(); } function no_sharing(c) { return size($c/parent::*) <= 1; }
2.4
Modelling the Dynamicity of Component Architectures
To support system evolution, some component models like Fractal provide dynamic reconfiguration abilities so as to modify systems during their execution. Dynamic reconfiguration are then based on architectural modifications (e.g., rebinding between components). We propose to extend the concept of integrity constraints on configurations to apply them to dynamic architectures. Semantics of reconfiguration operations. Reconfiguration operations are modelled as graph transformation operations. Primitive reconfiguration operations correspond to basic graph transformations, i.e. adding and removing nodes in the graph, adding and removing arcs between nodes or changing the value of a node property. Then, we consider the following primitive reconfiguration operations in the Fractal component model: – – – – – –
instantiation/destruction of components; addition/removal of a component in another component; binding/unbinding of component interfaces; life cycle modification of components (starting/stopping a component); renaming of components; setting attribute values of components.
A primitive reconfiguration operation op transforms a configuration A = (E, R) into another configuration A = (E , R ) where A and A are respectively the iniop tial and final state of the reconfigured system: A → A . A and A must be both valid. To constrain reconfigurations to only valid transformations of configurations, we specify preconditions and postconditions of primitives operations. For instance, as seen in Table 1, the precondition ’¬hasChildtrans (c, p)’ on the add operation is to check that the component to add is not already a subcomponent of the parent component. To check the reconfiguration, we used the same approach previously described in Section 2.3. First, preconditions and postconditions on reconfiguration operations are translated into the Alloy language so as to check that they do not violate any other integrity constraints in the model. A reconfiguration operation is specified in Alloy by a predicate (pred) on two configurations: an initial and a final configuration. Operations can be then simulated on any initial configuration. Then, the FScript [8] reconfiguration language is used along with FPath to specify reconfiguration operations in Fractal runtime systems.
82
M. L´eger, T. Ledoux, and T. Coupaye Table 1. Addition of a subcomponent add(Component p, Component c) Preconditions p, c ∈ E ∃i ∈ E, hasInterf ace(p, i) ∧ contentCtrlItf (i) ¬(p = c) ¬hasChildtrans (c, p) Postconditions E = E R = R ∪ {hasChild(p, c)}
The Alloy specification of the add primitive operation is given above with its translation addSaf e in FScript. The addSaf e Fscript action invokes a add operation which is an unchecked operation mapped on the Fractal API and which actually modifies the components in the system. pred add[a1, a2: Configuration, p, c: Component] { // Preconditions (c + p) in a1.components not p->c in a1.child isComposite[a1, p] not c->p in *(a1.child) // Elements sameElements[a1, a2] // Relations a2.child = a1.child + p->c a2.interface = a1.interface a2.attribute = a1.attribute a2.binding = a1.binding // Properties sameProperties[a1, a2] // Postconditions p->c in a2.child }
action addSafe(p, c) { -- Preconditions -- p must be a composite component assert($p/interface::content-controller)); -- p must not equals c assert(not($p == $c)); -- p must not be a descendant of c assert(size(intersection($p, $c/descendant::*)) == 0); -- Operation execution add($p, $c); -- Postconditions -- c must be a child of p assert(size(intersection($c, $p/child::*)) == 1); }
In conclusion, we have proposed in this section a definition of consistency for Fractal components (re)configuration. In the next section, we will explain how to guarantee reliable reconfigurations with a transactional support for fault tolerance.
3
A Transactional Approach for Reliable Reconfigurations
In order to ensure the reliability of dynamic reconfigurations, we adopted a transactional approach for dynamic reconfigurations. A reconfiguration is then a composition of operations demarcated in a transaction. Our transaction model supports fault recovery to maintain architecture consistency and concurrency of distributed reconfigurations. 3.1
A Flat Transaction Model for Dynamic Reconfigurations
We chose a transactional support for enabling reliable dynamic reconfigurations for several reasons. First, we think that well-defined transactions associated with
Reliable Dynamic Reconfigurations in a Reflective Component Model
83
the verification of constraints previously described is a mean to guarantee the reliability of reconfigurations i.e., the system stays consistent after reconfigurations even in the case of invalid operations. Then, a transactional support allows fault recovery: when a hardware crash happens during the runtime reconfiguration, the system comes back in the previous consistent state. Besides, a transactional support can manage concurrency between (distributed) reconfigurations by avoiding potential conflicts between reconfiguration operations. We use a flat transaction model for managing reconfiguration transactions and an implementation of ACID (Atomicity-Consistency-Isolation-Durability) properties in the context of reconfigurations to guarantee reliability. Several more complex transaction models have been defined [26] but flat transactions have proven to be sufficient and efficient in applications where transactions are relatively short-lived, the number of concurrent transactions is relatively small. Dynamic reconfigurations we consider appear to satisfy these hypotheses. Indeed, reconfigurations are essentially short-lived operations, and the level of concurrency for reconfigurations should be moderate: the system is not constantly reconfigured, otherwise, it would reduce its availability. Update techniques are an important issue related to database recovery since their efficiency have an important impact on the performance [25]. We propose two different modes of transaction updates. The first one is the immediate update which apply reconfiguration operations directly on the runtime architecture while the transaction is still in an active state. This technique is fast but costly in the case of fault recovery since the transactional support must execute undo/compensation operations on the real system to return to the initial state. The second mode is the deferred update which realizes a lazy copy of the current configuration. Every reconfiguration operation is then applied to the working copy until a successful completion of the transaction is assured, at which time the modifications are applied to the real system (if the transaction aborts, the copy is only destroyed). Our experiment results showed that immediate updates outperform deferred updates unless fault recovery is a frequent occurrence. 3.2
ACID Properties for Reconfigurations
The ACID properties were described in previous work [13], so we focus here on their general concepts. Atomicity. Either the system is reconfigured and the reconfiguration transaction commits (all the operations forming the transaction are executed) or it is not and the transaction aborts. If a reconfiguration transaction fails, the system comes back in a previous consistent state (rollback). To do this, we defined an undo model with compensation operations. Consistency. A reconfiguration transaction is a valid transformation of the system state i.e. it takes the considered system from a consistent state to another consistent state. A system is a consistent state if and only if it conforms to our consistency criteria: it does not violate integrity constraints (cf. Section 2).
84
M. L´eger, T. Ledoux, and T. Coupaye
Isolation. Reconfiguration transactions are executed as if they were independent. Results of reconfiguration operations inside a non-committed reconfiguration transaction are not visible from other transactions until the transaction commits or never if the transaction aborts. To manage concurrency between reconfigurations, we used a locking approach based on the semantics of reconfiguration operations. Durability. The results of a committed reconfiguration transaction are permanent: once a reconfiguration transaction commits, the new state of the system (both the architecture description and the component state) is persisted so that it can be recovered in case of major failures (e.g., hardware failures). To do this, transactions are journalized and the component state is check-pointed. 3.3
Runtime Checking of Integrity Constraints
A reconfiguration transaction can be committed only if the resulting system is consistent, i.e., if all integrity constraints on the system are satisfied. As seen in Section 2, we use FPath as a constraint language to express constraints including invariants, preconditions and postconditions. For example, the FScript reconfiguration code addSafe(c1, c2) will call the FPath precondition verifying that “a component c1 must be a composite component” (cf. code in Section 2.4). During a reconfiguration transaction, pre/postconditions of primitive operations are checked at each operation execution, whereas invariants (e.g., no sharing) are only checked at commit of transactions. That is to say a system can temporarily violate invariants during a transaction but it must be in a correct state after commit (cf. Figure 3). When it is detected, a constraint violation makes the transaction involved rollback. Begin
A0
Validation
Transaction
op1
checking preconditions
A1
op2
checking preconditions checking postconditions
A2
op3
...
opn
An
End
checking invariants
checking preconditions checking postconditions
checking postconditions
Fig. 3. Different steps to verify integrity constraints
4
Experiments
In this section, we first present a short overview of the transaction manager. Then, we realize a performance evaluation of our contribution with a small example. Finally, we propose an industrial use case in the context of system self-repair [12].
Reliable Dynamic Reconfigurations in a Reflective Component Model
4.1
85
A Modular Transaction Manager
A modular component-based framework has been developed to implement a transaction manager for dynamic reconfigurations in Fractal systems (cf. Figure 4). It is made up of several sub-components in charge of the different transactional functionalities, i.e., recovery, consistency, concurrency and durability management. The framework modularity allows programmers to enable or disable some transactional properties and to easily change their implementation. For instance, the sub-component ConcurrencyManager implements a pessimistic approach with strict two-phase locking [25]. We could replace it with an optimistic approach with validations to avoid the cost of locking especially when there are few concurrent reconfigurations in the system. This framework extends the Java reference implementation of Fractal, Julia [4], and some tools in the Fractal ecosystem such as the Fractal Architecture Description Language (ADL). TransactionManager Durability Manager
Recovery Manager
transaction API (JTA)
Transaction Monitor
Resource Manager
Consistency Manager
Concurrency Manager
Fig. 4. Global architecture of the Transaction Manager for dynamic reconfigurations
4.2
Performance Evaluation
In this section, we evaluate the additional cost of transactional mechanisms when a dynamic reconfiguration is successful. If the reconfiguration fails with our transaction manager, the recovery procedure is launched and the reconfiguration is rollback. If the reconfiguration fails without our transaction manager, the system becomes unusable. Then, a performance comparison when a reconfiguration is invalid has no meaning. The goal is to determine the ratio between execution times of a reconfiguration with transaction and without transaction. We choose a basic application for this micro-benchmark: the Fractal component HelloWorld is a composite with two primitive components (cf. Figure 5). The functional interface main of HelloWorld returns a message ”Hello” concatened with a value stored in an attribute of the primitive component Server. The reconfiguration use case consists in disconnecting the component Server from the component Client, remove it from its parent HelloWorld, then to add it and to reconnect it. We run the reconfiguration 100 times in the following
86
M. L´eger, T. Ledoux, and T. Coupaye HelloWorld main
main
Client
AttributeController printer printer
Server
Fig. 5. Fractal architecture of component HelloWorld
contexts: execution of Java reconfiguration code in a local JVM, execution of distributed Java code with RMI in several JVMs and execution of FScript reconfiguration code locally. The corresponding code in FScript is the following: action reconfig(cs) { server = $cs/child::server; client = $cs/child::client; unbind($client/interface::printer); remove($cs, $server); add($cs, $server); bind($client/interface::printer, $server/interface::printer); }
For our tests, we used the following machine configuration: Pentium M 1.86 GHz, 1 Go RAM, Microsoft Windows XP SP3, JRE v 1.6.0. We instantiated several JVMs on the same machine for the distributed reconfiguration. The transaction update technique used for this micro-benchmark is an immediate update (cf. Section 3). As previously explained, it is possible to enable or disable some transactional properties in our open transactional framework. This feature allows us to evaluate independently the different components of the transaction manager. We first choose to evaluate the performance of a minimal transaction manager. We only keep the support for recovery: primitive reconfiguration operations are logged in a journal so that it can be undone in case of rollback. Table 2 shows us the different results. The additional cost is essentially explained by the interception of primitive operations by the component Transactional Monitor for journalization. The overhead ratio of transactions is reduced in the context of distributed reconfiguration (Java RMI) or language interpretation (FScript) since the cost of interceptions is almost constant whereas distributive reconfigurations and reconfiguration expressed with an interpreted reconfiguration language are more costly than local Java reconfigurations. This additional cost could seem important but it should be put into perspective with the number of reconfigurations. Besides, it is the price to pay for more reliability. For information, the total cost of the same Java reconfigurations (first line of the table 2) with rollbacks in case of a simple reconfiguration failure (raising of an exception at the end of the reconfiguration) is 173 ms.
Reliable Dynamic Reconfigurations in a Reflective Component Model
87
Table 2. Comparison of execution time (in ms) of a reconfiguration with/without transaction Tests Without tx support (1) With tx support (2) Ratio (2)/(1) Reconfig. Java 37 112 3,03 Reconfig. Java with RMI 1297 1985 1,53 Reconfig. FScript 157 250 1,59
From the previous local reconfiguration (i.e., Java code in a local JVM), we define two new scenarii by introducing other transactional properties. Each property is implemented by a component from the transaction manager and is introduced separately: – Integrity constraints checking. The component Consistency Manager checks the model and profile integrity constraints (cf. Section 2) and also an applicative constraint specially defined for the component HelloWorld (specified by the ADL with a constraint tag). This applicative constraint forbid the composite HelloWorld to have more than two sub-components. – Concurrency Management. The componentConcurrency Manager implements a pessimistic approach with two-phase locking [25] to provide strong concurrency: each primitive operation locks Fractal element target of the reconfiguration. Table 3 shows us the different results where the ratio is calculated from the local Java execution scenario without transaction presented in Table 2. Table 3. Comparison of execution time (in ms) of a transactional reconfiguration according to its properties Tests With tx support Ratio (with tx)/(without tx) Integrity constraints checking 161 4,35 Concurrency management 177 4,78
The cost of constraint checking seems acceptable for this example with regard to the benefit of the property. However it should be noted that constraints checking is dependent on the number and complexity of constraints in the system. The cost of the concurency management comes mainly from the pessimistic strategy which requires to acquire some locks for reconfiguration operations. 4.3
Self-repair of Java EE Application Server
We propose to illustrate our contribution with a use case developed in the context of a French national project named Selfware3 . This use case shows how 3
http://sardes.inrialpes.fr/selfware
88
M. L´eger, T. Ledoux, and T. Coupaye
the Selfware platform has been used to manage clustered Java EE application servers, by applying self-repair (i.e., detecting defects and failures and taking corrective actions automatically). This scenario is used to repair a transient failure (e.g., memory overload) in a Java EE instance4 by restarting it. Our goal is to improve the reliability of the “reboot” operation so we propose to restart the server in a reconfiguration transaction respecting some integrity constraints. Integrity constraints. In the Selfware platform, every architectural element is wrapped in a Fractal component. Then, we specified integrity constraints at profile level both on the global architecture of the cluster and on given nodes in the cluster. Some examples of such constraints are the followings: – on the global cluster architecture: uniqueness of a JOnAS instance name in the domain, uniqueness of a master instance in the domain to manage the cluster, separation between Web and EJB tiers on different nodes; – on a local node: system resource availability (memory, CPU) to start a JOnAS instance, uniqueness of ports between JOnAS instances, restricted number of JOnAS instances on the same node. Scenario implementation. We choose to repair a memory overload in a JOnAS instance which can leads to a JVM crash by rebooting the server (i.e. the JVM) locally. We put a local constraint on each node of the cluster related to the minimum quantity of available memory which is needed when restarting a server on the node. The main autonomic control loop used to repair the transient failure is composed of the following elements (cf. Figure 6): – Sensors: A JMX agent (MBeanCmd tool) is used to monitor JOnAS instances in the cluster and allows to catch OutOfMemoryException thrown by failed JVMs; – Controller: the Reboot Manager subscribes to JMX events and when it is notified of a memory overload, it decides to reboot the failed application server; – Actuators: the Reconfiguration Service reboots the failed JOnAS instance on the same machine with respect to the integrity constraints checked by the Constraint Checking Service. The Reconfiguration Service executes the repair plan as a transactional reconfiguration, i.e. stops the failed server instance (a Fractal component) and restarts it. In the commit phase, the Constraint Checking Service makes sure integrity constraints are satisfied, i.e. it checks that the available memory on the node is above the minimum threshold given by the node constraint. If yes, the complete self-repair scenario has been executed to its end. If not, the Reconfiguration Service cancels the repair action and notifies the Controller of the constraint violation (and the impossible re-instantiation of the server on this node). The Controller could try in turn to re-instantiate the failed server on another node where more memory is available. 4
We used the JOnAS application server.
Reliable Dynamic Reconfigurations in a Reflective Component Model Autonomic Element ad verlo ory o mem tification no
Controller rebo
Reboot Manager
cons train t notifi violation catio n ot de
cisio n
Actuators
Sensors
Reconfiguration Service
MBeanCmd tool
Managed Element
constraint checking
JOnAS 4.8 Server
OutO fMem o
89
ryEx ce
ption
+ Integrity Constraints
Constraint Checking Service
ot rebo
actio
n
aints onstr get c
Fig. 6. Architecture of the self-repair management with integrity constraints
5
Related Work
Several component models support dynamic reconfiguration, but only a few of them take into account the reliability of the reconfigurations. Instead, most work on reliability and validation of component-based architectures is done in the context of static ADLs [15]. However, as identified by [21], ADLs are not enough. To support dynamic architectures, one also needs what the author calls an Architecture Modification Language (AML) to describe modification operations and an Architecture Constraint Language (ACL) to describe the constraints under which architectural modifications must be performed. In our proposition, FScript and FPath[8] play these two roles (AML and ACL, respectively). Mae (Managing Architectural Evolution) [23] is an architecture evolution environment using xADL [7] to specify architectures. A key difference between this work and ours is that reconfiguration is goal-based oriented: an ADL configuration is given as an objective and the difference with the current configuration is automatically performed respecting architectural invariants; the resulting patch is applied on the system. More recently, component models relying on reflective architectures to allow ad-hoc reconfigurations while supporting some kinds of guarantees have appeared. FORMAware [18] is relatively close to our work. This framework to program component-based application gives the possibility to constrain reconfigurations with architectural-style rules. A transaction service manages the reconfiguration by stacking operations. The main difference with our proposal is that our integrity constraints are more flexible than styles as they can be
90
M. L´eger, T. Ledoux, and T. Coupaye
applied at the model level or directly to specific instances with pre/postconditions and invariants. Rainbow [9] relies on the use of architectural styles to constrain dynamic adaptation of CBSE. The constraint language Armani [17] extends the ADL ACME [10] with first order logic to express architectural invariants. Constraints are periodically checked and adaptation strategies are executed in case of violation in order to reconfigure the system. A main difference with our solution is that reconfiguration are programmed statically in strategies whereas we can execute any non-anticipated reconfigurations at any time in the system. Plastik [3] is the integration of the reflective OpenCOM component model [6] and the ACME/Armani ADL. As in our approach, architectural invariants can be checked at run time and constraints are expressed at two levels (style and instance). However, we propose thanks to our transaction model a full support for concurrent reconfigurations to identify conflicts between operations. Aspect Oriented Modeling [16], a complementary technique to Model Driven Engineering, is another approach to ensure the validation of dynamic reconfigurations as in [19] for dynamic adaptive systems. A model is maintained at runtime to represent the running system. Reconfigurations rely on the weaving of model aspects rather than reconfiguration scripts. Transitions between the source and the target model are automatically generated thanks to model comparison. The OCL [20] syntax is used to specify invariants in a metamodel and woven models are checked against these invariants before adapting the runtime system. However, consistency between invariants is not guaranteed whereas the use of the Alloy language and its analyzer in our solution allows us to detect contradictions between constraints specified in configurations.
6
Conclusion
Evolution of software must be carried on without compromising their reliability. This is even more true for dynamic reconfigurations which are used to preserve system availability by modifying them at run-time. Dynamic reconfigurations can rely on good properties of component models such as modularity which defines the reconfiguration granularity. As a reconfiguration is a modification of a system state during its execution, it may potentially put this system in an inconsistent state. To make dynamic reconfigurations more reliable, we propose a transactional approach to ensure that system consistency is maintained despite run-time failures. We focus more particularly on the Fractal component model [4] because of its reflexivity, its intrinsic simplicity and its extensibility. Maintaining system consistency suppose to have a precise definition of what this property means in the context of component-based systems. The first step is then to provide a model of configurations and reconfigurations. Consistency is then specified by means of integrity constraints, i.e. configuration invariants and pre/post-conditions on reconfiguration operations. Alloy has been used as a specification language to model these constraints and to check that they are consistent between them. Constraints are then translated in FPath, a navigation language used as a constraint language in Fractal architectures to check the validity of integrity constraints on real systems at runtime.
Reliable Dynamic Reconfigurations in a Reflective Component Model
91
Fault tolerance in the reconfigured system is then ensured thanks to a transaction model adapted to dynamic, distributed and concurrent reconfiguration in component-based systems. We deal with fault recovery such as transaction rollback in case of constraint violation during reconfigurations. Moreover, system configurations are persisted so that it can be recovered after for instance a system or some hardware crashes. Finally, our model support the concurrent execution of several reconfigurations which are isolated by a locking mechanism. Our approach has been implemented in Fractal but could be generalized to other reflexive component models supporting dynamic reconfigurations. We plan notably for future work to transpose our (re)configuration and transaction models to another component model like OpenCom [6].
References 1. Abowd, G., Allen, R., Garlan, D.: Using style to understand descriptions of software architecture. SIGSOFT Softw. Eng. Notes 18(5) (1993) 2. Avizienis, A., Laprie, J.-C., Randell, B., Landwehr, C.: Basic concepts and taxonomy of dependable and secure computing. IEEE Trans. Dependable Secur. Comput. 1(1), 11–33 (2004) 3. Batista, T., Joolia, A., Coulson, G.: Managing dynamic reconfiguration in component-based systems. In: Morrison, R., Oquendo, F. (eds.) EWSA 2005. LNCS, vol. 3527. Springer, Heidelberg (2005) 4. Bruneton, E., Coupaye, T., Leclercq, M., Quema, V., Stefani, J.-B.: An open component model and its support in java. In: Crnkovi´c, I., Stafford, J.A., Schmidt, H.W., Wallnau, K. (eds.) CBSE 2004. LNCS, vol. 3054, pp. 7–22. Springer, Heidelberg (2004) 5. Cheng, B.H.C., de Lemos, R., Giese, H., Inverardi, P., Magee, J. (eds.): Software Engineering for Self Adaptive Systems. LNCS, vol. 5525. Springer, Heidelberg (2009) 6. Coulson, G., Blair, G., Grace, P., Taiani, F., Joolia, A., Lee, K., Ueyama, J., Sivaharan, T.: A generic component model for building systems software. ACM Trans. Comput. Syst. 26(1), 1–42 (2008) 7. Dashofy, E.M., van der Hoek, A., Taylor, R.N.: A highly extensible, XML-based architecture description language. In: Proceedings of Working IEEE/IFIP Conference on Software Architecture (WICSA 2001), Amsterdam, Netherlands (2001) 8. David, P.-C., Ledoux, T., L´eger, M., Coupaye, T.: FPath and FScript: Language support for navigation and reliable reconfiguration of Fractal architectures. Annals of Telecommunications 64(1), 45–63 (2009) 9. Garlan, D., Cheng, S.-W., Huang, A.-C., Schmerl, B., Steenkiste, P.: Rainbow: Architecture based self-adaptation with reusable infrastructure. Computer 37(10), 46–54 (2004) 10. Garlan, D., Monroe, R.T., Wile, D.: Acme: architectural description of componentbased systems. In: Foundations of component-based systems, pp. 47–67. Cambridge University Press, New York (2000) 11. Jackson, D.: Alloy: a lightweight object modelling notation. ACM Trans. Softw. Eng. Methodol. 11(2), 256–290 (2002) 12. Kephart, J., Chess, D.M.: The vision of autonomic computing. IEEE Computer 36(1), 41–50 (2003)
92
M. L´eger, T. Ledoux, and T. Coupaye
13. L´eger, M., Ledoux, T., Coupaye, T.: Reliable dynamic reconfigurations in the fractal component model. In: Proceedings of Workshop on Adaptive and Reflective Middleware (ARM 2007), pp. 1–6. ACM, New York (2007) 14. Lehman, M.M., Parr, F.N.: Program evolution and its impact on software engineering. In: Proceedings of International Conference on Software Engineering (ICSE 1976), pp. 350–357. IEEE Computer Society Press, Los Alamitos (1976) 15. Medvidovic, N., Taylor, R.N.: A classification and comparison framework for software architecture description languages. IEEE Transactions on Software Engineering 26(1), 70–93 (2000) 16. Mezini, M., Ostermann, K.: Variability management with feature-oriented programming and aspects. SIGSOFT Softw. Eng. Notes 29(6), 127–136 (2004) 17. Monroe, R.T.: Capturing software architecture design expertise with armani. Technical Report CMU-CS-98-163, School of Computer Science, Carnegie Mellon University, Pittsburgh, PA, USA (January 2001) 18. Moreira, R.S., Blair, G.S., Carrapatoso, E.: Supporting adaptable distributed systems with FORMAware. In: Proceedings of International Conference on Distributed Computing Systems Workshops (ICDCSW 2004), Washington, DC, USA, pp. 320–325. IEEE Computer Society, Los Alamitos (2004) 19. Morin, B., Barais, O., Nain, G., Jezequel, J.-M.: Taming dynamically adaptive systems using models and aspects. In: Proceedings of International Conference on Software Engineering (ICSE 2009), Washington, DC, USA, pp. 122–132. IEEE Computer Society Press, Los Alamitos (2009) 20. OCL 2.0 Specification (2005), http://www.omg.org/docs/ptc/05-06-06.pdf 21. Oreizy, P.: Issues in the runtime modification of software architectures. Technical Report UCI-ICS-TR-96-35, Department of Information and Computer Science University of California, Irvine (August 1996) 22. Oreizy, P., Medvidovic, N., Taylor, R.N.: Architecture based runtime software evolution. In: Proceedings of International Conference on Software Engineering (ICSE 1998), Washington, DC, USA, pp. 177–186. IEEE Computer Society, Los Alamitos (1998) 23. Roshandel, R., Van Der Hoek, A., Mikic-Rakic, M., Medvidovic, N.: Mae — a system model and environment for managing architectural evolution. ACM Trans. Softw. Eng. Methodol. 13(2), 240–276 (2004) 24. Szyperski, C.: Component Software: Beyond Object-Oriented Programming. Addison-Wesley Longman Publishing Co., Inc., Boston (2002) 25. Traiger, I.L., Gray, J., Galtieri, C.A., Lindsay, B.G.: Transactions and consistency in distributed database systems. ACM Trans. Database Syst. 7(3), 323–342 (1982) 26. Weikum, G., Schek, H.-J.: Concepts and applications of multilevel transactions and open nested transactions, pp. 515–553. Morgan Kaufmann Publishers Inc., San Francisco (1992)
Reactive Model-Based Control of Reconfiguration in the Fractal Component-Based Model Gwenaël Delaval and Eric Rutten INRIA / LIG, Grenoble, France {gwenael.delaval,eric.rutten}@inria.fr
Abstract. We present a technique for designing reconfiguration controllers in the Fractal component-based framework. We obtain discrete control loops that automatically enforce safety properties on the interactions between components, concerning, e.g., mutual exclusions, forbidden or imposed sequences. We use a reactive programming language, with a new mechanism of behavioural contracts. Its compilation involves discrete controller synthesis, which automatically generates the correct adaptation controllers. We apply our approach to the problem of adaptive ressource management, illustrated by the example of a HTTP server. Keywords: adaptive systems, reconfiguration control, components, contracts, model-based approach, reactive programming, discrete controller synthesis, resource management.
1
Motivation and Example Application
1.1
Model-Based Control for Fractal
The Fractal component-based approach. Fractal [4] is a modular component model that can be used with various programming languages, to design, implement, deploy and reconfigure systems and applications, from operating systems to middleware platforms and to graphical user interfaces. It is equipped with a hierarchical structure, and puts an emphasis on reflexivity, in order to support adaptation and reconfiguration. Components are the basic construct enabling the separation of interface and implementation. They support the explicit representation of the software architecture, which is essential for adaptivity and manageability. It is the basis for performing run-time software reconfiguration and system supervision. Management of components then consists of monitoring, control and dynamical reconfiguration of the architecture. The composite structure offers a uniform construct for this: introspection functionalities enable monitoring the state of system, while re-configuration actions allow to change it. A whole range of levels of control is supported, from black box with no control, to full fledged introspection. A lifecycle controller defines the adaptive behavior of components.
This work is partially supported by the Minalogic MIND project.
L. Grunske, R. Reussner, and F. Plasil (Eds.): CBSE 2010, LNCS 6092, pp. 93–112, 2010. c Springer-Verlag Berlin Heidelberg 2010
94
G. Delaval and E. Rutten
Adaptive systems and resource management. Computing systems are proliferating, in a great variety of environments, typically embedded systems. They have to be more and more adaptive: they must perform reconfigurations in reaction to changes in their environment concerning e.g., power supply, communication bandwidth, quality of service, or also typically dependability and fault tolerance for a safe execution. Another motivation for adaptative and autonomic systems is the complexity of administration, and the need for automated techniques replacing manual or ad hoc management [16]. The run-time management of this dynamical adaptivity is the object of research on ways to design and implement adaptation strategies. One approach is autonomic computing [15], where functionalities are defined at operating system or middleware level, for sensing the state of a system, deciding upon and performing reconfiguration actions. The management of dynamical adaptivity can be considered as a closed control loop, on continuous or discrete criteria. Figure 1(a) shows how, on the basis of monitor information and of an internal representation of the system, a control component enforces the adaptation policy, by taking decisions w.r.t. the reconfiguration actions to be executed [16]. The design of control loops with known behaviour and properties is the classical object of control theory. Applications of continuous control theory to computing systems have been explored quite broadly [14]. In contrast, logical aspects, as addressed by discrete control theory, or even by hybrid systems combining continuous and discrete dynamics, have been considered only recently for adaptive computing systems [23]. Even if qualitative aspects are considered for long e.g., in quality of service issues (QoS) [17], the technical approach did not involve the benefits of control techniques. We address this with the BZR programming language [10] as shown in Figure 1(b). The class of dynamical changes addressed is the run-time switching between configurations characterized by stable states, in which a given computing activity is executed. As an example of application, adaptation mechanisms can be used for the dynamical management of resources, in a variety of ways. It is a way to handle the coordination of the shared access to constrained resources, which can be exclusive or have a bounded capacity, or have constraints in the sequences in which they can be used. We concentrate on logical aspects of the adaptation control, with abstract modelling of discrete levels of consumption of quantitative resources. policy / strategy
BZR program
decision
DCS ctrlr
system representation
automaton model
monitor
execute
monitor
execute
managed system
managed system
(a) Adaptive system.
(b) BZR controller.
Fig. 1. Adaptation control and its BZR programming
Reactive Model-Based Control of Reconfiguration
95
Control based on reactive models. One level of adaptive systems is related to events and states, defining execution modes or configurations of the system, with changes in the architecture, and in the activation of components. Reactive languages based on finite state automata are widely used for these aspects, like StateCharts [13], or StateFlow in Matlab/Simulink , or UML variants. Their underlying model, transition systems, is also the basic formalism for discrete control theory, which studies closed-loop control of discrete-event and logical aspects of systems [6]. Different reactive languages exist, like StateCharts mentioned before, and the languages of the synchronous approach [3]: Lustre, Esterel or Lucid Synchrone [8]. They are used industrially in avionics and safety-critical embedded applications design [22]. They offer a coherent framework for specification languages, their compilers, with distributed code generation, test generation and verification. In this framework, a basic technique used for the design of control loops is Discrete Controller Synthesis (DCS) [21,6]. It consists in, from a controllable system, and a behavioural property, computing a constraint on this system so that the composition of the system and this constraint satisfies the property. An automated DCS tool exists [18], connected to reactive languages. It has been applied to the automatic generation of task handlers [19], and integrated in a domain-specific language [11]. It was also applied to fault-tolerance, in an approach where fault recovery is seen as the reconfiguration of computing activities from a given placement on the execution architecture, by exploiting its redundancy, and switching and migrating to another one where the faulty processor is not used any more [12]. More recently the BZR language has been defined with a contract mechanism, which is a language-level integration of DCS [1,10]. The user specifies possible behaviours of a component, as well as safety constraints, and the compiler synthesises the necessary control to enforce them. The programmer does not need to design it explicitly, neither to know about the formal technicalities of the encapsulated DCS. It is briefly explained in (see Section 3), with more detail in Appendix A. Contributions. We present an integration of reactive model-based techniques for the control of reconfiguration, in the Fractal component-based framework. We concentrate on the lifecycle control, and present a structural association with reactive nodes in the BZR language. It is a language-based solution, to generate correct by construction controllers for the discrete loop, for safety properties on the interactions of components. In the event and state-based aspects where it is applicable, the DCS formal method is made usable by non-experts, as it is encapsulated in a programming language and compiler. The generated code (C or Java) can be concretely integrated in the run-time executives. We make a study of the example of a component-based HTTP server. This way, designers can benefit from, on the one hand, the Fractal approach to component-based systems, and on the other hand, the BZR language for the automated synthesis of reactive control. In the following, the example application is presented in Section 1.2. Brief background on the Fractal component-based model and on reactive models and
96
G. Delaval and E. Rutten
DCS is given in Sections 2 and 3. The structural integration of reactive control in Fractal is described in Section 4, illustrated with the application, and Section 5 sketches execution-level integration. 1.2
Example of a HTTP Server
We consider a HTTP server, illustrated in Figure 2, with its adaptation requirements. It is a variation [7] of the Comanche HTTP server used as an example in tutorials1 for the Fractal component-based middleware platform [4]. Incoming requests are read by the RequestReceiver component, which transmits them to the RequestAnalyser component. The latter can forward them to the RequestHandler component, which queries a farm of file servers to solve the request, through a RequestsDispatcher. RequestAnalyzer can also consult a cache in the CacheHandler component, in order to master the response time and keep it as short as possible. A Logger component enables logging of transactions, and can be connected to the RequestsAnalyser. The latter can monitor e.g., a high number of similar requests.
Comanche Cache Handler
Logger
RequestHandler
File Server 1
Requests
Requests
Requests
File
Receiver
Analyser
Dispatcher
Server 2
BackEnd
Fig. 2. The Comanche HTTP server architecture
The available degrees of dynamical reconfiguration are that the File Servers, CacheHandler and Logger components can be activated or deactivated. The resources involved in the system and its dynamical management are the consumption in energy, and an exclusive resource shared by the CacheHandler and Logger. Requirements for these evolutions define the adaptation policy: 1. 2. 3. 4.
the CacheHandler is activated in case of high number of similar requests; the number of deployed file servers must be adapted w.r.t to the overall load; a logging request by the system administrator, it should not be denied; logging and cache handling should be exclusive, due to the access to some other resource.
These rules must be enforced by the adaption controller as in Figure 1(a). 1
http://fractal.ow2.org/tutorial/
Reactive Model-Based Control of Reconfiguration
2
97
The Fractal Component-Based Model
We briefly introduce the basics of the Fractal component model [4], in order to define the structures to which we propose a behavioral extension. 2.1
Components and Composites
A Fractal component, as shown in Figure 3, is equipped with an interface giving accesses to the component, of two kinds: server interfaces accept incoming operations invocations, while client interfaces support outgoing operation invocations. It has a content, which can consist of a finite set of sub-components. Special interfaces concern control aspects, which are handled in the membrane. control interfaces membrane content server interfaces client interface
Fig. 3. A Fractal component
The other mechanism in Fractal to define architectures is binding, which is connecting interfaces of components: this is the only way to make them communicate. A primitive binding connects one client interface with one server interface; composite bindings can be defined also: bindings are components themselves. Figure 4(a) gives an example for the BackEnd component of Section 1.2. It features three sub-components, connected by appropriate bindings. RequestAnalyser and Logger are base components, while RequestHandler is a composite, itself decomposed into binded sub-components. A Fractal component is equipped with a membrane, which supports interfaces to introspect and reconfigure its internal features. It is composed of several controllers, provides an explicit and causally connected representation of the content, and performs control on the sub-scomponents, e.g., suspending, checkpointing, resuming activities, installing bindings. 2.2
Reconfiguration Control in Fractal
There are several levels of control, from base components (black boxes, with no introspection or reconfiguration capability), to components exposing their external structures (clients and servers available), and to components exposing their internal structures, and providing for reconfiguration actions. Examples of possible controllers are managing:
98
G. Delaval and E. Rutten
– – – –
attributes (through get and set operations), bindings (binding and unbinding client interfaces to server interfaces), contents (adding and removing subcomponents), and, most interestingly to us, lifecycle, where explicit control is given over the main behavioral phases of a component.
Back End
Back End Logger
Request Analyser
Request Handler
(a) Configuration with logger.
Cache Handler
Request Analyser
Request Handler
(b) Configuration with cache.
Fig. 4. Two configurations of the Back End component
Reconfiguration actions which we will consider in this work will be adding and removing a component, and binding and unbinding primitive connections. Implementations of Fractal exist in different contexts, academic and industrial, and embedded in different host languages, namely C (with several variant, like Cecilia, or Think targeted at embedded systems, or their industrialization MIND2 ) or Java [4] (Julia). Concerning reconfiguration mechanisms, libraries for introspection and actions have been proposed in Fscript3 [9]. Modelling the example of Section 1.2 in Fractal involves constructing a component architecture simply following the informal drawing of Figure 2, for the different configurations. For the case of the Back End component, Figure 4(a) shows, in a classical Fractal graphical syntax, a configuration with the logger active, while Figure 4(b) shows another configuration, where only the cache is active. The reconfiguration themselves are described by giving the actions requested in order to perform them. In this case, reconfiguring the Back End component from configuration 4(a) to configuration 4(b) involves the following sequence: remove the Logger, unbind it from the RequestAnalyser, add the CacheHandler, bind it with the RequestAnalyser. Fractal and adaptive systems, and behavioral models have been associated in the litterature, following a variety of approaches e.g. parallel frameworks [5] or formal models [2,20]. Our work is specific in that it concentrates on reconfiguration control, and proposes to relate this aspect of Fractal with the synchronous approach to reactive systems and particularly DCS techniques, which we present next, in order to design correct by construction control loops. 2 3
http://mind.ow2.org http://fractal.ow2.org/fscript/
Reactive Model-Based Control of Reconfiguration
3
99
Programming Reactive Systems in BZR
In this section we first briefly introduce the basics of the Heptagon language, to program data-flow nodes and hierarchical parallel automata [8]. We then describe the BZR language, which extends Heptagon with a new contract construct [1,10]. As for all reactive languages introduced in Section 1.1, the basic execution scheme is that at each reaction a step is performed, taking input flows as parameters, computing the transition to be taken, updating the state, triggering the appropriate actions, and emitting the output flows. 3.1
Data-Flow Nodes and Mode Automata
Figure 5(a) shows a simple example of a Heptagon node, for the control of a task that can be activated by a request r, and according to a control flow c, put in a waiting state; input e signals the end of the task. Its signature is defined first, with a name, a list of input flows (here, simple events coded as Boolean flows), and outputs (here: the Boolean act). In the body of this node we have a mode automaton : upon occurrence of inputs, each step consists of a transition according to their values; when no transition condition is satisfied, the state remains the same. In the example, Idle is the initial state. From there transitions can be taken towards further states, upon the condition given by the expression on inputs in the label. Here: when r and c are true then the control goes to state Active, until e becomes true, upon which it goes back to Idle; if c is false it goes towards state Wait, until c becomes true. This is a mode automaton [8] in the sense that to each state we associate equations to define the output flows. In the example, the output act is defined by different equation in each of the states, and is true when the task is active. We can build hierarchical and parallel automata. In the parallel automaton, the global behaviour is defined from the local ones: a global step is performed synchronously, by having each automaton making a local step, within the same global logical instant. In the case of hierarchy, the sub-automata define the behaviour of the node as long as the upper-level automaton remains in its state.
delayable(r,c,e) = act r and not c act = false
Wait act = false
Idle
e act = true Active
r and c c
(a) Mode automaton node.
f (x1 , . . . , xn ) = (y1 , . . . , yp ) (eA , eG ) = cf (x1 , . . . , xn , y1 , . . . , yp ) assume eA enforce eG with c1 , . . . , cq y1 = f1 (x1 , . . . , xn , c1 , . . . , cq ) ··· yp = fp (x1 , . . . , xn , c1 , . . . , cq ) (b) BZR contract node.
Fig. 5. Example of programs in graphical syntax
100
3.2
G. Delaval and E. Rutten
Contracts in the BZR Language
This new contract construct encapsulates DCS in the compilation of BZR [1,10]. Models of the possible behaviours of the managed system are specified in terms of mode automata, and adaptation policies are specified in terms of contracts, on invariance properties to be enforced. Compiling BZR yields a correct-byconstruction controller, produced by DCS, as illustrated in Figure 1(b), in a user-friendly way: the programmer does not need to know technicalities of DCS. As illustrated in Figure 5(b), we associate a contract to a node. It is itself a program cf , with its internal state, e.g., automata, observing traces, and defining states (for example an error state where eG is false, to be kept outside an invariant subspace). It has two outputs: eA , assumption on the node environment, and eG , to be guaranteed or enforced by the node. A set C = {c1 , . . . , cq } of local controllable variables will be used for ensuring this objective. This contract means that the node will be controlled, i.e., that values will be given to c1 , . . . , cq such that, given any input trace yielding eA , the output trace will yield the true value for eG . This will be obtained automatically, at compilation, using DCS. Also, one can define several such nodes with instances of the same body, that differ in assumptions and enforcements. Without giving details [10] out of the scope of this paper, we compile such a BZR contract node into a DCS problem as in Figure 6. The body and the contract are each encoded into a state machine with transition function (resp. T rans and T rC), state (resp. State and StC) and output function (resp. Out and OutC). The contract inputs XC come from the node’s input X and the body’s outputs Y , and it outputs eA , eC . DCS computes a controller Ctrlr, assuming eA , for the objective of enforcing eG (i.e., making invariant the subset of states where eA ⇒ eG is true), with controllable variables c1 , ...cq . The controller then takes the states of the body and the contract, the node inputs X and the contract outputs eA , eG , and it computes the controllables Xc such that the resulting behaviour satisfies the objective. The BZR compiler is implemented on top of the Heptagon compiler and the Sigali DCS tool [18]. Its performance is subject to the natural complexity of the algorithms, which is exponential just as the model checking algorithms are, but there are arguments in favor of its scalable use:
contract XC
TrC
StC
OutC
eA , eG
body X
Ctrlr
Xc
Trans
State
Out
Fig. 6. BZR contract node as DCS problem
Y
Reactive Model-Based Control of Reconfiguration
101
– the technique is applied to a relatively small fragment of the complete system, which is only its reactive, state-based control part; all the data-oriented code of the system, which is usually its vast majority, is not taking part in this controller synthesis, thanks to the separation of concerns offered by the components structure; – state space exploration algorithms used in model-checking as well as in DCS have known notable progress, due to years of research on efficient codings, such as symbolic techniques and BDDs (Binary Decision Diagrams); as a result the size of systems amenable to these techniques has grown substantially; this point, related to the previous one, makes us claim that we can handle the specialized control part extracted from large systems; – it automatically generates an executable control solution, correct by construction, which is to be compared with manual programming, verification and debugging, which would be extremely difficult, as soon as the system is too large to be designed by a small team. It is then even more costly in time, and can involve days or weeks of engineering time. Moreover, the use of modular DCS can help to reduce significantly this cost [10]. The execution cost of the controller is very small. Integration of our targetindependent language and compiler in a development process follows the general scheme as in Figure 12 in the case of Fractal [4], as explained in Section 5. The control part is extracted from the adaptive system, in the form of a BZR program. Its compilation is made in derivation of the main system development process, and produces the synthesized constraint on controllables, composed with the sequential C code for the automata. They are assembled and linked back into the global executive. More detail on the BZR language is given in examples in the next sections, illustrated with nodes and contracts, and in Section 5.1 for its implementation. Essentials on DCS are given in Appendix A, and a concrete BZR syntax example in Appendix B.
4 4.1
Associating Reactive Control with a Fractal Model General Approach
Our extension to Fractal consists of the addition of elaborate behavioral controllers, in the form of labelled transition systems and contracts, which were not present previously in Fractal, where only an informal life cycle was defined. We follow the Fractal hierarchical components structure, and describe the way we associate, at each level of component, automata-based models of the behavior of the part relevant for reconfiguration in this control scope i.e., the activation and deactivation, and binding and unbinding of the direct sub-components. The simple principle is illustrated for base components in Figure 7(a): the control defined in the membrane is modelled with automata, which can be composed in parallel when they describe different aspects of the behavior. In particular, there is an explicit representation even of sub-components that are not activated but
102
G. Delaval and E. Rutten control interfaces membrane content server interfaces client interface
(a) Behavior of a base component.
(b) Behavior of a composite.
Fig. 7. Automata modelling the behavior of Fractal components
could be. In order for a predictive control to be applied, the behavioral model must feature a representation of absence. For composites, which can be dynamical or not, Figure 7(b) sketches the composition of the behaviors of the component itself, with the parallel composition of behaviors of sub-components. It can be noted that the synchronous composition is associative and commutative. 4.2
Base Components
Every component has a lifecycle controller, which indicates its activation state, as shown in Figure 8(a). The adds and removes, received from the reconfiguration automata, lead respectively to state Active and to the inactive state Idle. For optimisation, this is useful essentially if the upper level composite is dynamic, i.e., does perform activations and deactivations of this component. Other aspects of the component behavior, even if they are not distinguished in Fractal component architecture, can be meaningfully modelled in automata or equations. In our example, Figure 8(a) features equations associated to the states of the mode automaton, defining cons, which indicates the level of consumption of a resource (here, it is related to energy); in the active state it is defined by a constant cons_act, whereas in the inactive state it is null. In the application example, costs when active are given the following values, for the cache: 50, for the logger: 30, and for the file server 2: 20. This lifecycle node will be instanciated for each of the components, as shown in the case of the CacheHandler in Figure 9 in
lifecycle(add,remove,cons_act) = act, cons Idle remove
act = false cons = 0
act = true cons = cons_act
(a) Base component controller.
dense = false
Norm not d
add Active
observer(d) = dense
d Dense
dense = true
(b) Observer in RequestAnalyser.
Fig. 8. Models of base components behaviors
Reactive Model-Based Control of Reconfiguration
103
node cache_handler(add,remove:bool) returns (active:bool;cons:int) let (active,cons) = lifecycle(add,remove,50); tel Fig. 9. Instanciation of the lifecycle node for CacheHandler
concrete BZR textual syntax. This is done similarly for requests_dispatcher, logger, file_server1, file_server2, and requests_receiver. The RequestAnalyser component can detect phases with a high number of similar requests: it has a second automaton shown in Figure 8(b), which distinguishes the two states Norm and Dense, upon input d. 4.3
Composites
Static composites. Composites can be associated with the same behavioral information as base components. The transitions of their lifecycle controller, have to be propagated to the sub-components, for them to be added and removed, according to the composition semantics chosen for Fractal. Behavior models of sub-components are composed in parallel, as in the right part of Figure 10 for the example of the RequestHandler, where sub-nodes are invoked for the requests dispatcher, file servers and request analyser. Additional equations and automata can be defined as well. Typically, in our example, the costs of sub-components are summed up in order to define the composite cost, with the equation defining cons in terms of values coming from sub-nodes. request_handler(up, down) = cons (active_rd, cons_rd) = H2 requests_dispatcher(); (active_fs1, cons_fs1) = file_server1(); up / down / add_fs2 remove_fs2 (active_fs2, cons_fs2) = file_server2(add_fs2, remove_fs2); H1 cons = cons_rd + cons_fs1 + cons_fs2; Fig. 10. Model of the Request Handler composite
Reconfigurable composites. If the composite is static i.e., not reconfiguring explicitely its sub-components architecture, then its behavior is sufficiently defined by the elements described above. For a dynamically reconfigurable component, we associate an additional explicit automaton where, basically, states correspond to configurations, and transitions describe which reconfigurations are possible. Parallel automata can handle independent aspects or dimensions of the configurations. Exchanges of events between parallel automata can define synchronizations between allowed reconfigurations. We apply the BZR programming methodology: first describe possible behaviours with imperative automata,
104
G. Delaval and E. Rutten
then specify control objectives in the declarative contract. In the framework of Fractal, we can have several levels of specification for a reconfiguration policy. Reconfiguration policy by automata. This consists simply in programming in terms of automata i.e., specifying explicitely the requested behavior. The left part of Figure 10 shows the reconfiguration automaton for RequestHandler, that handles two configurations for the file servers that are deployed or shut down; in H2 two are up, in H1 just one; transitions are taken w.r.t. inputs up and down, and the file server 2 is added or removed accordingly. There is no contract at this level, but we will see later that up and down will be used as controllables. Another example is in Figure 11 for BackEnd. The concrete code for this part of the example can be seen in appendix B. Possible behaviors are described in a reconfiguration automaton, handling logging and cache with three configurations: cache active (C), logging active (L), or none (N). The fact that this automaton is programmed with no state with both active takes care of the exclusion requirement 4 of Section 1.2. Transitions are conditioned by two variables: the uncontrollable l (coming from the user), and c, which will be used as a controllable. If l is true then the configuration starting the logger is taken, and if it is false, then the logger is stopped; the cache can be activated when c is true, only if the logger is not. This programming takes care of requirement 3. Such programming, not making use of contracts, or DCS, can be validated with the classical methodology of verification, typically with model-checking. Reconfiguration policy by logical contract. More originally, specifications with contracts amount to specify declaratively the control objective, and to have an automaton describing possible behaviors, rather than writing down the complete correct control solution. The basic case is that of contracts on logical properties i.e., involving only Boolean conditions on states and events. In the upper part of Figure 11, the contract is itself a program, with three controllable variables, defined in the with part, used for enforcing the objectives, and its own equations. One of them, the cache policy (requirement 1 of Section 1.2), is an example of simple logical property, and is encoded as : pcache = (dense and not active_logger) implies active-cache
which can be encoded in primitive Boolean operators as4 : pcache = not (dense and not active_logger) or active_cache
The control objective then consists in making this predicate invariantly true i.e., constraining behaviors to stay invariantly within the states where this predicate is true. There is no special assumption made in the environment, on the expected value of inputs. This is simply stated as: assume true enforce pcache Here, BZR compilation and DCS produce the dynamical, state-dependent constraint on c such that the cache will be controlled following the requirement 1. 4
a ⇒ b ≡ ¬a ∨ b.
Reactive Model-Based Control of Reconfiguration
105
back_end(l, dense_req) = cons pcache = not (dense and not active_logger) or active_cache; pload = (cons <= 60); assume true enforce (pcache and pload) with up, down, c : bool (cons_rh) = requests_handler(up, down); c & not l / add_c (active_cache, cons_cache) = N C cache_handler(add_c, remove_c); not c & not l / remove_c (active_logger, cons_l) = l / remove_c, add_l l / add_l logger(add_l, remove_l); (dense, active_ra, cons_ra) = L not l & not c not l & c requests_analyzer(dense_req); / remove_l cons = cons_cache + cons_l + cons_ra; Fig. 11. The BZR node for the Back end component
Reconfiguration policy by contract with weights. A more elaborate form of contract can be used, involving weight functions associated to states. This technique is less powerful than timed or hybrid automata, but also less costly w.r.t. the synthesis algorithms, and it has the benefit of allowing for some expression of quantitative aspects of a system, in terms of static constant values and expressions on them. They are transformed back into logical properties, in the sense that we consider invariants on the respect of bounds. An example of a weights contract is seen in the upper part of Figure 11. One equation of the contract program encodes the load-related adaptation aspect of the requirements of Section 1.2 (requirement 2): pload = (cons ≤ 60) This control objective concerns quantitative weights, but is actually also a logical invariance, so it can be treated as above: assume true enforce pload Here, BZR compilation and DCS will produce the constraint on up and down such that the file server will be controlled according to requirement 2. 4.4
Structural Control of Fractal Components
This shows how a controller for adaptation policies defined by the requirements above, involving mutual exclusion and insertion of reconfiguration tasks, can be obtained with our mixed imperative / declarative method and language. Behaviors are described locally, structurally following the hierachy of components in Fractal. Their composition is made by the compilation. The adaptation policy can be programmed in the automata, but it can also be described declarativly, and the controller is derived automatically and correctly by DCS. Improvements in user-friendly useability could be the definition of a library of predefined patterns for reconfiguration automata and objectives; further in that direction, they
106
G. Delaval and E. Rutten
could serve to define a domain-specific language, where BZR would be hidden as an internal format [11]. On the side of control techniques, exploiting weights for optimal control, is not yet covered in BZR compilation, but the tools and models exist and could be integrated [19].
5
Execution-Level Integration
Figure 12 describes the development process of our method. The complete BZR program, comprising both automata and contracts, is first extracted from the Fractal specification. The BZR compiler then produces both sequential C code, and Boolean equations (modelling the BZR program) and synthesis objectives. These equations and objectives allow the DCS tool to produce a constraint on free controllable variables. These constraints are resolved at execution time by a resolver embedded in the C code. This C code is then linked back with the one generated by the Fractal compilation.
Fractal spec.
extract components automata & contract
Middleware link generated code executive (with constraint (C, Java) resolution)
BZR compiler synchronous compiler
Bool. eq. & obj.
seq. C code
DCS constraint
Fig. 12. Development process for BZR, in the case of Fractal
5.1
BZR Code and Compilation
As we saw before, each Fractal component is given a BZR node. The modular compilation of this language allow then to obtain, through the process exposed in Figure 12, one C function “step” for each Fractal component. It handles the lifecycle of its associated component, calling in turn the “step” functions of the active sub-components. The DCS is performed on the synchronous composition, performed at compilation time, of automata of all sub-nodes. The semantics of the language guarantees the equivalence of the compilation towards executed C code, and the one towards Boolean equations for DCS. Concerning performance aspects, the synthesis time is clearly the bottleneck of our approach, and the final controller size is, for the same reasons as the synthesis time, exponential in the size of the initial program, but, as we mentioned in Section 3.2, we can handle non-trivial systems in practice. T This controller consists of sequences of conditionals (translation of binary decision diagrams), and its online evaluation is polynomial in the size of the initial program. For the example of this paper, both DCS and on-line resolution of the controller take only few ms (standard Pentium, 2.33 GHz).
Reactive Model-Based Control of Reconfiguration
5.2
107
Linking Executable Codes
The step function obtained by BZR compilation handles the lifecycle by: 1. keeping a persistent local state, modelling the life cycle of the actual component, to help the decisions of the synthesized controller, 2. actuating, on each call, on the actual state of the component via the outputs of the node, e.g, by means of calls of reconfiguration scripts, so as to keep this actual state coherent with the internal state of the BZR node. This second point can be fullfilled, e.g., by associating to outputs of each local node reconfiguration actions programmed in FScript [9]. We give the action associated to the output add_logger of the back_end node in Figure 13. action addFS2(root) { logger = new("Logger"); set-name($logger,"logger"); add($root,$logger); bind($root/child::analyzer/interface::logger, $logger/interface::logger); start($logger); } Fig. 13. Reconfiguration actions programmed in FScript
5.3
Simulation and Typical Scenario
Our BZR program can be compiled and executed, or simulated with a chronogram graphical simulator5 as shown in Figure 14. In the left part, on top, the user interface comprises buttons where the user clicks inputs (true or false values for the two variables). Outputs of the current step are displayed just below, as well as the current step number since the initial state. The lower part shows the simulation control panel, with a button triggering one step. The right part shows the graphical chronogram display of execution traces, with values at each step. A typical scenario illustrates the intervention of the controller on the system, so that control objectives are preserved. Starting from (Norm, H2, N), when d occurs (step 11), by requirement 1 (first part of the contract) the cache is started, and by requirement 2 (second part of the contract) server f 2 is stopped (otherwise the available load would be overshot). Hence we go in state (Dense, H1, C). When l occurs (step 17), then by requirements 3 and 4, programmed in BackEnd, the cache is stopped, the log is started, and by requirement 2 (second part of the contract) the server f 2 can be started again, and we go to (Dense, H2, L).
5
Courtesy of Verimag.
108
G. Delaval and E. Rutten
Fig. 14. Simulation
6
Conclusion
Contributions. We propose a technique to design reactive model-based controllers for reconfiguration in the Fractal component-based framework. We consider them in terms of a discrete controller synthesis problem, solved in the compilation of a programming language ensuring logical safety properties. We obtain discrete control loops for adaptive systems, that can be used e.g., for the safe management of resources: we illustrate the approach with a HTTP server example. Discussions. It can be noted that in the current Fractal, there seems to be no other way to describe reconfiguration than by enumerations the sequences of actions performing them, rather than having a higher-level formalim to describe the reconfiguration (what to reconfigure) independently of their implementation of actions (how to do it). We believe that a Mode Automata formalism [8] might be an interesting, well structured way to define configurations as graphs encapsulated in states of automata, that can be composed hierarchically and in parallel; but this perspective is out of the scope of this paper. As DCS is a costly algorithmic operation by nature, typically exponential in the number of state variables, it is important to consider techniques to face the combinatorial explosion. It can be noted however that modern symbolic techniques are able to handle in short computation times, in the order of minutes, state spaces of millions of states. Still, scaling up involves specific efforts, and our language is defined in such a way that when a contract node is decomposed into sub-nodes, themselves equipped with sub contracts, DCS is decomposed modularly into local DCS problems, which can be solved independently [10]. At each level, sub-contracts serve as an abstracted model of sub-components in the computation of the local controller. Although not explicitely exploited in this paper, this modularity support is expected to fit particularly well in the hierarchical component-based architectures of Fractal.
Reactive Model-Based Control of Reconfiguration
109
Perspectives. Ongoing work on the integration of Fractal and BZR includes building a more elaborate and refined behavioral model of Fractal components, implementing a concrete integration at the ADL level with the C implementation of Fractal, exploiting hierarchy and modularity in BZR [10], treating concrete case-studies in cooperation with industrial partners in the MIND project, and enriching the models with e.g., reachability or optimization aspects [19]. Perspectives concern ongoing work on the integration of our technique with several targets, other than Fractal [4] object of this paper: FPGA-based reconfigurable architectures, administration loops in a Java virtual machine, and the Orccad control systems design environment.
References 1. Aboubekr, S., Delaval, G., Rutten, E.: A programming language for adaptation control: Case study. In: Proc. of the 2nd Workshop on Adaptive and Reconfigurable Embedded Systems, APRES 2009 (2009) 2. Barros, T., Ameur-Boulifa, R., Cansado, A., Henrio, L., Madelaine, E.: Behavioural models for distributed fractal components. Annals of Telecommunications 64(1), 25–43 (2009) 3. Benveniste, A., Caspi, P., Edwards, S., Halbwachs, N., Le Guernic, P., de Simone, R.: The synchronous languages twelve years later. Proc. of the IEEE 91(1) (January 2003) 4. Bruneton, E., Coupaye, T., Leclercq, M., Quema, V., Stefani, J.-B.: The fractal component model and its support in java. Software – Practice and Experience (SP&E) 36(11-12) (September 2006) 5. Buisson, J., André, F., Pazat, J.-L.: A framework for dynamic adaptation of parallel components. In: ParCo 2005, Málaga, Spain, September 13-16 (2005) 6. Cassandras, C., Lafortune, S.: Introduction to Discrete Event Systems. Kluwer Acad. Publ., Dordrecht (1999) 7. Chauvel, F., Barais, O., Borne, I., Jézéquel, J.-M.: Composition of Qualitative Adaptation Policies. In: 23rd IEEE/ACM Int. Conf. on Automated Software Engineering - ASE 2008, L’Aquila, Italy (September 2008) 8. Colaço, J.-L., Pagano, B., Pouzet, M.: A Conservative Extension of Synchronous Data-flow with State Machines. In: ACM Int. Conf. on Embedded Software (EMSOFT 2005) (September 2005) 9. David, P.-C., Ledoux, T., Léger, M., Coupaye, T.: FPath and FScript: Language support for navigation and reliable reconfiguration of fractal architectures. Annals of Telecommunications 64(1), 45–63 (2009) 10. Delaval, G., Marchand, H., Rutten, E.: Contracts for modular discrete controller synthesis. In: Proc. of the ACM Conf. on Languages, Compilers and Tools for Embedded Systems, LCTES (2010), http://hal.inria.fr/inria-00436560 11. Delaval, G., Rutten, E.: A domain-specific language for multi-task systems, applying discrete controller synthesis. J. on Embedded Systems (2007), http://dx.doi.org/10.1155/2007/84192 12. Girault, A., Rutten, E.: Automating the addition of fault tolerance with discrete controller synthesis. Int. J. on Formal Methods in System Design 35(2) (October 2009), http://dx.doi.org/10.1007/s10703-009-0084-y 13. Harel, D., Naamad, A.: The statemate semantics of statecharts. ACM Trans. Softw. Eng. Meth. 5(4) (1996)
110
G. Delaval and E. Rutten
14. Hellerstein, J., Diao, Y., Parekh, S., Tilbury, D.: Feedback Control of Computing Systems. Wiley-IEEE (2004) 15. Kephart, J.O., Chess, D.M.: The vision of autonomic computing. IEEE Computer 36(1) (2003) 16. Krakowiak, S.: Middleware Architecture with Patterns and Frameworks, ch. 10. Electronic book (2009), http://sardes.inrialpes.fr/~krakowia/MW-Book 17. Lee, C., Lehoczky, J., Siewiorek, D., Rajkumar, R., Hansen, J.: A scalable solution to the multi-resource qos problem. In: RTSS 1999: Proc. of the 20th IEEE RealTime Systems Symposium, Washington, DC, USA, p. 315 (1999) 18. Marchand, H., Bournai, P., Le Borgne, M., Le Guernic, P.: Synthesis of discreteevent controllers based on the Signal environment. Discrete Event Dynamic System: Theory and Applications 10(4) (October 2000) 19. Marchand, H., Rutten, E.: Managing multi-mode tasks with time cost and quality levels using optimal discrete control synthesis. In: Proc. of the 14th Euromicro Conf. on Real-Time Systems, ECRTS 2002 (2002) 20. Poulhiès, M., Pulou, J., Sifakis, J.: Buzz: analyzable embedded component-based software. In: Workshop on Component Models for Embedded Systems (COMES), Sweden (June 2008) 21. Ramadge, P.J., Wonham, W.M.: Supervisory control of a class of discrete event processes. SIAM J. on Control and Optimization 25(1), 206–230 (1987) 22. Esterel tech. Scade: model-based development environment dedicated to safetycritical embedded software (2010), http://www.esterel-technologies.com/ 23. Wang, Y., Lafortune, S., Kelly, T., Kudlur, M., Mahlke, S.: The Theory of Deadlock Avoidance via Discrete Control. In: ACM Symp. on Principles of Programming Languages (POPL 2009) (January 2009)
A
The BZR Language, and Discrete Controller Synthesis
Behavior of BZR programs can be represented by a transition system, as illustrated in the inside box of Figure 15: a transition function T rans takes as inputs X as well as the current state value, and produces the next state value. The latter is memorized by State for the next step. The output function Out takes the same inputs as T rans, and produces the outputs Y . Discrete controller synthesis (DCS) allows to use constructive methods, that ensure, off-line, required properties on the system behavior. DCS is an operation that applies on a transition system (originally uncontrolled), where inputs X are partitioned into uncontrollable (X u ) and controllable variables (X c ). It is applied with a given control objective: a property that has to be enforced by control. In this work, we consider essentially invariance of a subset of the state space. The purpose of DCS is to obtain a controller, which is a constraint on values of controllable variables X c , function of the current state and the values of uncontrollable inputs X u , such that all remaining behaviors satisfy the property given as objective. The synthesized controller is maximally permissive, it is therefore a priori a relation; it can be transformed into a control function. Figure 15 shows the transition system of the inside box, as yet uncontrolled, composed with the synthesized controller Ctrlr, which is fed with uncontrollable inputs X u and the current state
Reactive Model-Based Control of Reconfiguration
Xu
Ctrlr
Xc X
Trans
State
Out
111
Y
Fig. 15. Controlled transition system
value from State, in order to produce the values of controllables Xc which are enforcing the control objective. The transition system then takes X = X u ∪ X c as input and performs a step. node back_end (request_logger,dense_requests:bool) returns (add_cache,remove_cache,add_logger,remove_logger,add_fs2, remove_fs2,active_fs2,active_cache,active_logger:bool; cons:int) contract var pcache,pload:bool; let pcache = not ((false fby dense_requests) & not (false fby request_logger)) or active_cache; pload = cons <= 60; tel assume true enforce (pcache & pload) with (up,down,c:bool) var cons_rh,cons_cache,cons_logger,cons_ra:int; active_ra:bool; let (cons_rh,add_fs2,remove_fs2,active_fs2) = requests_handler(up,down); (active_cache,cons_cache) = cache_handler(add_cache,remove_cache); (active_logger,cons_logger) = logger(add_logger,remove_logger); (active_ra,cons_ra) = requests_analyzer(); cons = cons_rh + cons_cache + cons_logger + cons_ra; automaton state Nothing do add_cache = not request_logger & not c; remove_cache = false; add_logger = request_logger; remove_logger = false; until request_logger then Logger | not c then Cache state Logger do add_cache = not request_logger & not c; remove_cache = false; add_logger = false; remove_logger = not request_logger; until not request_logger & c then Nothing | not request_logger & not c then Cache state Cache do add_cache = false; remove_cache = request_logger or c; add_logger = request_logger; remove_logger = false; until request_logger then Logger | c then Nothing end tel
Fig. 16. Concrete BZR code for the controller of the BackEnd component
112
B
G. Delaval and E. Rutten
BZR Model of the Control of the HTTP Server
The node in Figure 16 illustrates the concrete syntax of the BZR language, by showing the code for Figure 11. After the input/output signature, the contract part is based on local Boolean variables, and defines, in the with part, the controllable variables that will be used in the body. The body has its own local variables e.g., integers used to compute the cumulated cost for consumption management. It begins with invocations of sub-nodes for the controllers of the sub-components. An equation defines consumption at this level in the component architecture simply as the sum of consumptions underneath. Then an automaton is programmed in the textual syntax: each state is named, and it is associated with equations executed at each step as long as the control is in the state. The until part specifies the transition conditions and targets: when the condition evaluates to value true, the then part gives the name of the state where the control will be from next step; several transitions going out of a state are separated by |.
Enabling on Demand Deployment of Middleware Services in Componentized Middleware Yan Li, Minghui Zhou*, Chao You, Guo Yang, and Hong Mei Key Laboratory of High Confidence Software Technologies, Ministry of Education Software Institute, School of Electronics Engineering and Computer Science, Peking University, Beijing 100871, China {liyan05,zhmh,youchao06,yangguo10}@sei.pku.edu.cn, [email protected]
Abstract. With the prevalence of middleware, the amount of middleware services (abbr. services) grows increasingly large. However, an application typically utilizes a small subset of the services. In consequence, middleware is often criticized for deploying all the possible services at bootstrap. This paper advocates an on demand approach of deploying services, which is that the middleware automatically and dynamically deploys a minimal set of services according to applications’ demands, so that low resource consumption, high customizability and short boot time can be obtained. By encapsulating the service as a set of independent deployable components with clearly specified dependencies, we address the problem of on demand service deployment by dint of managing dependencies among the components at runtime and propose a framework supporting the management of application, middleware components and runtime dependencies. Finally, the framework is implemented in a Java EE middleware to demonstrate its feasibility and effectiveness. Keywords: middleware service, deployment, component dependency management, customizability.
1 Introduction Generally speaking, middleware systems, such as Java EE, .Net, and CORBA, can be defined as a set of middleware services (interchangeable with services) that facilitate the development of distributed applications in heterogeneous computing environments [1], like naming, logging, security, and transaction services. In recent years, with the wide adoption of middleware in a broad spectrum of application domains, middleware is evolving rapidly with an ever richer service set. For example, Java EE specification family covers 20 middleware services. Each kind of service is independently justifiable for sound business reasons [2], and has one or more implementations, which possess similar basic functions but stress on different aspects (e.g. performance). *
Corresponding author.
L. Grunske, R. Reussner, and F. Plasil (Eds.): CBSE 2010, LNCS 6092, pp. 113–129, 2010. © Springer-Verlag Berlin Heidelberg 2010
114
Y. Li et al.
On the other side, an application typically utilizes only a small subset of the services [3], and it may demand specific service implementation under different contexts. On traditional middleware, the services deployed are independent of the hosted applications: the middleware starts up with all its services deployed no matter whether they are required by the applications or not. Consequently, with the amount of services growing considerable large, this way of deploying services is often criticized for the following deficiencies. First, system resources (e.g., CPU, memory) are not efficiently utilized as many unnecessary services are deployed, and thus the middleware performance may be degraded. Second, the services offered, notwithstanding their generality, may be functionally unfit for a specific application. Finally, the efficiency of middleware-based application development is reduced, because the more services are deployed, the more configurations application developers need to take care of, and some services may be even unrelated with the applications. Moreover, valuable time is wasted in the frequent middleware rebooting at the stage of application development, since more and more services are loaded. To alleviate the afore-mentioned problems, component technique [4] has been leveraged into the middleware construction by many approaches [5][6][7][8]. A common characteristic of the approaches is that components are units of management abstraction for middleware services: by implementing a set of standard lifecycle interfaces, the components control the start/stop of services, as a result a global logical management view is achieved and administrators could start/stop the services as needed. However, at the code level, service implementations are tangled together, so all the services are loaded into memory together at bootstrap, which is resource and time consuming; moreover, the start/stop operations have to be executed manually. In this paper, we advocate an on demand approach of deploying services, which is that the middleware automatically and dynamically deploys a minimal set of services according to applications’ requirements, so that low resource consumption, high customizability and short boot time can be obtained. The main contributions of this paper include: first, we analyze the problem of on-demand deployment and address it by dint of components’ runtime dependency management techniques: in this paper, middleware components (interchangeable with components), which encapsulate service implementations and clearly specify dependencies with others, are units of deployment, and managing the dependencies among components allows the middleware to deploy only the requisite components at runtime. Second, we propose a framework to enable on demand service (un/re)deployment in middleware, which contains a group of mechanisms covering the management of applications, middleware components, and runtime dependencies. Finally, we implement the framework in a Java EE middleware to demonstrate its feasibility and effectiveness. The rest of this paper is organized as follow: Section 2 gives a detailed analysis of the service deployment problem on the middleware. Section 3 and Section 4 present the design and the implementation of the framework respectively. Section 5 evaluates the framework through a set of experiments. Section 6 discusses the related work. Section 7 concludes the paper.
Enabling on Demand Deployment of Middleware Services
115
2 Problem Analysis Our work focuses on the Java EE middleware which has been widely adopted in the enterprise computing. In this section, after presenting the current status of deployment on Java EE middleware, we depict the typical scenarios for on demand service deployment, and then discuss the runtime dependency management which is essential for realizing on demand service deployment. 2.1 Current Deployment Support in Java EE Until now, Java EE has addressed the middleware concerns for more than a decade and the amount of services prescribed in the Java EE specification has reached 20. Java EE specification has addressed the issues of application deployment, i.e., how to deploy the application into EJB/Web container. However, it falls short with respect to the deployment of services: not only is not a standardized deployment facility for services specified, but also the specifications, in fact, place any prescriptions on neither how to modularize the services nor how to manage the dependencies among the services and the applications, which are fundamental to the dynamic and automatic deployment of services. Even though component concept has been introduced into the middleware construction for a long time, components are more used for managing the services rather than encapsulating them as independent deployable units: each service is usually provided with a Java object which implements a set of predefined management interfaces via calling the service implementation, and these Java objects are called components which are used by the administrators to start or stop the services as needed. However, the codes of the services are still tangled together and even wrapped into a single module under the limited isolation support of Java package mechanism. Consequently, previous Java EE products incline to load all the services as a whole at bootstrap, which consumes a lot of time and resources. Though nowadays, some modularization techniques have been exploited by some mainstream middleware to isolate the services from each other, there is still a lack of runtime dependency management among the services and the applications, so the service deployment must be manually done; otherwise all the services have to be carried along. Additionally, for some services, there may be various implementations offered by different third parties, for example, ObjectWeb JORAM[23], Codehaus ActiveMQ[24] and SONIC OpenJMS[25] are all message service implementations. Nevertheless, service implementations are usually determined after the Java EE product is released due to the insufficient services modularization and dependency management [9]. The pre-postulated service implementations might be either functionally redundant, or insufficient, or incomplete for a specific application. 2.2 On Demand Middleware Service Deployment: Typical Scenarios As application requirements become more diverse and specific, it is desirable for Java EE middleware to dynamically and automatically (un)deploy the services on the applications’ demand. In such a middleware, services are encapsulated as a set of independent deployable components with clearly specified dependencies, which make
116
Y. Li et al.
it feasible to isolate the services from each other at the code level and to deploy them individually with Java class loading mechanism. Some typical scenarios for on demand middleware service deployment are sketched in Figure 1: (a) When starting up, the middleware only loads the kernel components from the repository. (b) When deploying a new application, the middleware first automatically configures itself by dynamically deploying the components which constitute the services demanded by the application, and then deploys the application. (c) When the middleware replaces (or updates) a current service implementation with a new one, the components that depend on the current implementation automatically come into an inactive status, and they will not resume until the middleware reconnects them with the new one. Then, the middleware dynamically undeploys the current service implementation and deploys the new one. (d) After undeploying an application, the middleware automatically and dynamically undeploys the unused components without interrupting the others.
Fig. 1. On demand service deployment: typical scenarios
Compared with current approach of service deployment, the on demand approach allows middleware tuning a convenient execution environment for each newly launched application without bloating itself with generic services that cost many resources. Service implementations deployed on the middleware vary with the requirements of the hosted applications, rather than stay fixed after the bootstrap. 2.3 Runtime Dependency Management To achieve on demand service deployment, it is important for the middleware to properly manage the dependencies among the middleware services and the applications at runtime, as it is otherwise impossible to determine the implications of adding, removing or replacing a service, let along to dynamically customize a particular runtime environment for the applications. Specifically, dependency management enables the middleware to find the minimal set of components which constitute the required services when deploying an application, and correspondingly to find the maximal set of unused components when undeploying an application, and also to detect the associated components when replacing a service implementation. To effectively manage the dependency, we recognize four basic issues:
Enabling on Demand Deployment of Middleware Services
117
1. Explicitly specifying the direct dependencies of a component. It is not difficult to identify the components that each component directly depends on. All these direct dependency specifications together implicitly embrace a whole graph of the middleware in terms of dependencies and components, which is necessary for dependency management. 2. Resolving the dependency. Applications point out their required services whose constituent components may depend on other components. Hence, middleware should be capable of resolving all the necessary components based on the components’ dependency specifications. 3. Binding or unbinding the dependencies dynamically. As the components enter or leave the middleware at runtime, the dependencies between them and other components should be established or destroyed accordingly. 4. Tracing the dependency dynamically. Both applications and components may leave the middleware at any time. Middleware should be aware of the components depended by or depending on them, so that it can adapt to the changes. For example, after an application is removed, it is resource efficient for the middleware to undeploy the components that are no longer in use.
3 Framework Design In this section, we first give an overview of the framework which enables the middleware service deployment as application demands. Upon introducing the concept of component and dependency in middleware, we explain the design of each part of the framework in detail. 3.1 Framework Overview As shown in Figure 2, application manager which is composed of application monitor and application deployer is the entry of the framework. As soon as application monitor detects an application is placed to or removed from the middleware’s deployment directory, it will invoke application deployer to perform the (un/re)deployment. During the (un/re) deployment, application deployer delegates middleware service deployer (abbr. service deployer) to dynamically (un)deploy the services accordingly. Service deployer decides which components of services to (un)deploy basing on the application demand and its maintained middleware’s dependency graph. The directed dependency graph tracks the middleware components that have been deployed (represented as the squares tagged with “C”) and the dependencies between them, which include static dependency (represented as the real lines) and dynamic dependency (represented as the dash lines). The details of middleware components and dependencies are presented in Section 3.2. All the middleware components are stored in the component repository. Before a service is deployed, dependency resolver recursively finds out all the necessary components ahead through resolving the repository specification. Component loader is responsible for loading a component from component repository and unloading the components from the middleware. Component configurator and binder configure the properties of the loaded components as needed and establish the dependencies.
118
Y. Li et al.
Fig. 2. Framework overview
The processes of application (un/re)deployment work as follows: When (re)deploying an application, application deployer first acquires the required services by parsing the application’s specification, and then invokes service deployer to prepare the required services beforehand. After some pre-processing steps, service deployer asks for all the components needed to be deployed from dependency resolver, and then invokes component loader to retrieve the components from the repository and establish the static dependencies at the same time, and finally calls component configurator and binder to configure the components and establish the dynamic dependencies. Thereafter, service deployer updates the dependency graph with the newly-added components and dependencies and returns the control back to the application manager. Finally, the application deployer (re)deploys the application into the EJB/Web container. When undeploying an application, application deployer first removes the application from the EJB/Web container, and then invokes service deployer to dynamically unload the idle components owing to the application undeployment. When receiving the invocation, service deployer explores the dependency graph to find out which components are no longer in use and unloads them one by one. Then, service deployer updates the dependency graph. 3.2 Middleware Component and Dependency Before diving into the introduction of each part of the framework, we would like to explain the concept of middleware component in our framework, and further identify two distinct kinds of dependencies between the components. An individual middleware component is a unit of independent deployable module which encapsulates related functionalities, and interacts with others by means of well-defined interfaces. Components are viewed as a general abstraction that can accommodate all forms of functionalities found in the middleware, and composite component is not supported from the middleware point of view, that is, the dependency graph maintained by service deployer (See section 3.4) is a flat structure. This stipulation is necessary to avoid burdening service deployer with complex maintenance work and having the middleware lose track of global visibility because of the
Enabling on Demand Deployment of Middleware Services
119
components which are hidden within the composite components. However, disallowing the composite component in the middleware does not preclude the use of hierarchical composition from the application point of view. Each middleware service implementation is logically represented to the applications as a composition of a group of components. For example, there are two components in the middleware dependency graph, which provides the functionalities of authentication and authorization respectively, whereas a security service implementation including these two components is logically exposed to the applications. With this arrangement, service deployer retains global visibility of all the components and dependencies efficiently, while applications can request the service as if it were a single component, which eases the middleware-based application development. For each service implementation, there is a specification file which describes the implementation’s name, version and the service type it belongs to as well as the name and the version of its entry components. By entry components, we mean the components are not depended on by other components belonging to the service implementation. To deploy a service implementation, the middleware automatically loads and binds the service’s constituent components and their dependent components from the entry components. As for the dependency between the components, there exist two kinds which are static dependency and dynamic dependency. Static dependency is hard coded in the source codes of the components, that is, classes in the component import the interfaces in others. The dependent components are prerequisite for successfully loading the component. Static dependencies usually live between the component and its libraries. Because the declarations in the source codes are so obscure that it is unfeasible to manage the static dependency, we demand each component explicitly specify its static dependencies in its description file. Dynamic dependency is merely declared in the description file, that is, components rely on their description files to declare their provided and required functionalities which are represented as well-defined interfaces, and service-oriented interaction pattern, i.e., publish-discover-bind, is employed to locate and bind the components at runtime. The concept of dynamic dependency was first introduced by Cervantes and Hall [19] through a service-oriented component model. It promotes the idea that a requester is not tied to a particular provider; instead, providers are substitutable at runtime as long as they comply with the contractually defined interfaces. This increases the flexibility of the middleware architecture to a great extent, because explicit bindings between two particular service implementations are not necessary, and the bindings are inferred at runtime from the dynamic dependencies declared in the description files. The support of dynamic dependency enables the middleware to be built as an abstract composition of all kinds of services that only becomes concrete at runtime in responds to the applications’ demands. Subsequently, we give a concrete example of using the static and dynamic dependency. As stipulated by the Java EE specification [26], EJB container relies on the Message service to send point-to-point messages as well as the publish-subscribe messages for Java EE applications. In JO2nAS, we extract the interfaces between the EJB container and message service, and pack the interfaces into a component (abbr. interface component) that is separated from the components of EJB container (abbr. container components) and the components of message service (abbr. message
120
Y. Li et al.
components). Both the Message components and the container components have static dependencies on the interface component, and meanwhile, they state either the provision or the requirement of the functionality defined by the interface component in their own description files accordingly. At run time, the mechanism provided of our framework constructs the dynamic dependency between EJB container and message service, which are variable: the default implementation of message service is ObjectWeb JORAM, however, another message service implementation, such as Codehaus ActiveMQ and SONIC OpenJMS, can substitute the default one as long as it implements the defined interfaces. 3.3 Application Manager Application manager consists of application monitor and application deployer. Application monitor periodically checks the deployment directory of the middleware and picks out the applications whose states are newly-added, modified or removed. Then, application monitor invokes appropriate operations on application deployer, which are deployment for the newly-added applications, redeployment for the modified applications and undeployment for the removed applications. Application deployer accomplishes the (un/re)deployment operations through coordinating with the service deployer (see Section 3.4) and the EJB/Web container, the processes of which have been illustrated in Section 3.1. To achieve on demand service deployment, each application should specify its required services; an example is shown in Figure 3. Except the type tab, the tabs of name and version are optional. Application deployer parses this file to choose the appropriate service implementations, and a default one is chosen if only the service type is specified. To (un)deploy the application into (from) the EJB/Web container, application deployer employs the mechanism that has been presented in [10].
Fig. 3. An application’s service requirement file
3.4 Middleware Service Deployer Besides coordinating other parts of the framework, middleware service deployer does pre-processing and post-processing for (un)deploying services respectively. Pre-processing. First, service deployer checks whether there are conflicts among the applications demands. Typically, it is not a good practice for the middleware to run
Enabling on Demand Deployment of Middleware Services
121
several implementations for a type of service simultaneously due to functionality duplication and resource conflicts (e.g., system ports). Consequently, the service deployer is designed to settle the application demand conflict when possible, or otherwise report it explicitly. To illustrate the conflict checking process clearly, suppose a deployed application A requires a transaction service and a specific message service implementation M1, and the middleware deploys a default transaction service T1 and the appointed M1; if application B that requires a specific transaction service implementation T2 is being deployed, service deployer will find that application B’s requirement is compatible with current service configurations since T1 is optional, and then substitute T1 with T2 for application B; if application C that requires a specific message service implementation M2 is being deployed, service deployer will find that application C’s requirement is conflicted with current service configurations, and then report a warning to the middleware administrator to solve it. Next, service deployer matches an application’s service requirement file (see Section 3.3) with the service implementations’ specification files (see Section 3.1) in order to select the appropriate service implementations and acquire their entry components. Meanwhile, service deployer records the application-service dependencies in a bi-direction way: one is in the straight direction, i.e., the services which each application depends on; the other is in the reverse direction, i.e., the applications which depend on each service. Each service implementation is mapped to its entry components, as composite components are not supported from the middleware point of view, which is explained in section 3.2. Post-processing. Service deployer first gets the components whose dependencies are directly affected from the application-service dependency. Then, beginning from these components, the service deployer searches the dependency graph it holds to locate the idle components. In the dependency graph, similar with the applicationservice dependency, the component-component dependencies which are gained from dependency resolver (See Section 3.6) are also recorded in a bi-direction way. 3.5 Component Repository Component repository is used to store the middleware components. To facilitate discovering the required components, the repository keeps a specification file which records the components’ meta-info. The meta-info for each component contain: (a) component name and version: two components with the same name and version are considered to be identical; (b) the provisions of the components: both the packages it exports and the interfaces it implements should be manifested; (c) the requirements of the components: each component should specify the packages of other components and the interfaces it depends on. Middleware developers can (un)install components into (from) the repository via component installer. Each time when installing a component, component installer first parses the component’s description file to acquire its meta-info, and writes them into the repository’s specification file, and then puts the component into a directory. Generally, the directory is unique by linking with the component identifiers, e.g. repository URL\component name\version. The component uninstallation process is in reverse, which is that the installer removes the component first and then deletes its meta-info from the repository specification file.
122
Y. Li et al.
3.6 Others Dependency resolver takes a middleware component’s name as an input, and returns the dependency sub-tree beginning from the component. Although component repository specification file records a component’s dependent components, these dependent components may depend on others, and so on; hence, dependency resolver needs to resolve dependencies recursively. Component loader supports independent (un)loading of middleware components. By independent (un)loading, it means the loaded components should be isolated from each others, that is, packages of one component except the exported ones cannot be accessed by others. In the Java language, the classes are loaded by the class loader mechanism [11]. A class loader does not allow two classes with the same fully qualified name coexisted, and the classes loaded by one class loader can access each other. In JVM, there is only a default class loader, and if all middleware components share the same class loader, not only is component isolation missing, but also it prevents different middleware services from importing libraries of different versions, which happens frequently in the middleware. Consequently, the framework provides a single class loader for each middleware component, which acts as the component boundary at runtime. When a component is loaded, its class loader forms a class loading delegation network with other components’ class loaders, which enables the component to visit the others’ exported packages. In this way, the component loader finishes the static dependency establishment. Each middleware service may have some attributes to be configured, for example, transaction service has listening port attributes and timeout attributes. All these attributes are distributed among the service’s constituent components. Component configurator reads the attributes from the middleware’s configuration file or the management console, and configures the components at runtime, which has already been widely supported by today’s middleware. Component binder is responsible for establishing the dynamic dependencies between the components. It binds the components by injecting the instance of a component which implements the required interfaces into the instance of the other component, the mechanism of which is named dependency injection [12]. Moreover, component binder deals with the publishing and discovering of components.
4 Implementation We implement the framework in a Java EE compatible middleware, named JO2nAS[27]. JO2nAS is a cooperation project between JOnAS development team and PeKing University aiming merging the best of JOnAS and PKUAS middleware into a configurable and flexible middleware. In JO2nAS, we first divide the middleware into components and establish the static or dynamic dependencies between them through analyzing and reorganizing the packages in the middleware. Figure 4 lays out a part of component dependency topology on JO2nAS, where the rectangle with solid lines represents a middleware component while the rectangle with dashed lines represents a service implementation, the solid line stands for the static dependencies while the dashed line stands for the dynamic
Enabling on Demand Deployment of Middleware Services
123
Fig. 4. An incomplete dependency topology of the components on JOn2AS
dependencies. Some simplifications have been made for the sake of understanding. Among the components, Service-API, Java EE API, and Commons are fundamental libraries which are depended statically by many other components. For example, the Service-API component, which encompasses all the interfaces between the services, is depended on by the entry components of each service. These fundamental libraries, in accompany with some basic middleware functional components (e.g., the service deployer), are treated as the middleware kernel and loaded at bootstrap. JO2nAS comes into the running mode as soon as the kernel and the configured services are deployed. Each service implementation constitutes of one or more components, such as JOnAS_Security, and these constituent components may further depend on some specific libraries, such as JDOM which is depended by PKUAS EJB container. OSGi R4 platform [28] not only defines a dynamic component model for Java, which supports the static dependency and dynamic dependency between components, but also enhances Java class loader mechanism to support independent (un)loading of components. Hence, we integrate Apache Felix[29], an open source implementation of OSGi R4 platform, in our framework. In addition, each component is packaged to an OSGi bundle, and the static dependency is specified through explicitly exporting/importing packages of bundles, and the dynamic dependency is specified through OSGi publish-find-bind model. More details about the integration of OSGi into JO2nAS can be found in literature [13]. Application manager extends the existed application hot deployment on JOn2AS, which deals with the dynamic and automatic (un)deployment of applications into (from) the EJB container. An invocation to service deployer is inserted before (after) the (un)deployment of an application into the EJB container. To maintain the runtime dependencies on the middleware, service deployer holds four key-value maps: the first two are for the component-component dependency; the others are for the application-service dependency. Take the component-component dependency for example, the two maps record the dependency in a bi-direction way, where the key is the name of component C, while the value is a name list of components either depending on or depended by the component C. In virtue of these maps, upon an application undeployment, service deployer adopts the following algorithm (as shown in Figure 5) to undeploy the idle components recursively.
124
Y. Li et al.
Fig. 5. An algorithm to undeploy the idle components upon an application undeployment
Since components are realized as OSGi bundles, we construct the component repository based on the OSGi bundle repository specification [30], which specifies the meta-information which should be registered for each bundle in the repository specification file and the file’s XML schema. By resolving the repository specification file, we implement the dependency resolver which returns a complete dependency sub-tree with key-value map.
5 Evaluation In this section, we evaluate the performance improvement and overhead brought by the proposed framework via a set of experiments on JO2nAS. In the experiments, the computer for running JO2nAS is equipped with Inter(R) Core(TM)2 Duo CPU V3.00GHz and 2GB RAM, and runs Microsoft Windows XP professional SP3 and Sun JDK 1.5.0_07. We use the performance monitor tool provided by the operating system to monitor the memory occupation of JO2nAS. First, to evaluate the performance improved by our framework at bootstrap, we start up the middleware without any applications, and calculate the bootstrap time and memory occupation of the middleware. The results are shown in Table 1, which have been averaged over ten runs. Compared with the original JO2nAS (denoted as " JO2nAS "), the new version with our framework (denoted as JO2nAS II) has considerably reduced the bootstrap time and memory occupation, since the services no longer need to be deployed at bootstrap. Additionally, we measure the memory occupation of JO2nAS II with all the services deployed at bootstrap, which is 88.36 MB. Compared with JO2nAS, we believe that the memory overhead brought by the framework is affordable.
Enabling on Demand Deployment of Middleware Services
125
Table 1. Performance comparison at bootstrap JO2nAS
JO2nAS II
Improvement
Bootstrap time (s)
47.70
14.82
68.93%
Memory occupation (MB)
85.26
56.97
33.18%
Next, we evaluate the our framwork‘s performance when deploying an application at runtime. We choose a standard three-tier Java EE appliation from Java EE 5 tutorial[31], which is an online bookstore and uses several services including transaction, security, database, mail, messages, EJB/Web containers. During the experiment, we first started up JO2nAS and JO2nAS II respectively, and only the middleware kernel is deployed when JO2nAS II started. After both JO2nAS and JO2nAS II came into a stable state, the application was deployed onto each of them, and before deploying the application, JO2nAS II dynamically deployed the required middleware services with the support of our framework. To show the memory occupation variation upon deploying an application clearly, Figure 6 omits the startup segements and begins a little early before the application deployment. In Figure 6, it shows that their memory occupations both increase a lot, and the memory increase of JO2nAS II is much sharper than that of JO2nAS, becasue besides the application, some services were deployed at that time. However, the memory occupation of JO2nAS II is consistently lower than that of JO2nAS.
Fig. 6. Memory occupation comparison when deploying an application
Because JO2nAS II needs to deploy the required services before deploying the application into EJB container, the application deployment time of JO2nAS II is longer than that of JO2nAS. To evaluate the performance overhead of our framework, we find the average deployment time for a servcie is about 1 second, and most of the time is spent in resolving the dependencies of a service's consitituent components. In our ongoing work, the dependency resolving is expected to be optimized significantly
126
Y. Li et al.
by resolving dependencies offline and reading the information directly at runtime, so we believe the performance overhead of our framwork is affordable comparing with its benenifts.
6 Related Work Although a variety of research efforts have been devoted to deployment [14], most of the work focused on the deployment of application components in distributed heterogeneous environments [15][16][17]. Similarly, the Java EE specification in terms of deployment does not involve the deployment of services on the middleware, but only regulates the deployment of an application into the EJB/WEB container. Compared with them, our focusing on the deployment emerges with ever increasing middleware services. It is not a new idea to leverage component techniques in the middleware. Previous approaches [5] [6] [7] [8] usually use component as a management entry for the middleware services, which allows middleware administrators starting or stopping the services at runtime according to the application demands. Different from these approaches, our work utilizes the component to encapsulate service implementations as independently deployable units, that is, the implementation codes of different services are isolated physically, which is important to enable independent loading of service at runtime. Moreover, we make explicit dependency management between the middleware components as well as between the applications and the middleware components, which serves the foundation for on demand deployment of services. Through deploying the services on demand, customizability is achieved by the middleware. As far as we know, there are some approaches also working on the customization of middleware services. For example, Geronimo supports middleware administrators to propose deployment plans to declare the services that should be loaded [32]; Zhang and Jacobsen apply aspect techniques to refactor the middleware as a set of features, so that the middleware can be woven according to the application demands [18]. A limitation of these approaches is that the types and implementations of services have to be determined before booting, and if the applications want to change the service implementation at runtime, the middleware needs to be recompiled or at least reboot. Comparatively, our work supports dynamic customization. Nevertheless, these approaches provide us with valuable experiences and insights about how to componentize the middleware. Dynamic configuration of the loaded components is also an important feature of operation system, especially for embedded systems and Wireless Sensor Networks (WSN) [20]. Nano-kernel[21] is a operation system framework for sensor devices in WSN, and its key idea is that data are decoupled from devices and stored in the operating system kernel, and the kernel is responsible for routing the calls between the modularized devices, that is, the direct dependencies between devices are converted to the dependencies between the kernel and devices. However, this is not practical for Java EE middleware, because many middleware services are not developed by the middleware vendors themselves, and they are not bound to a specific middleware kernel. Costa et.al [22] proposes a component model for embedded systems, which emphasizes on how to componentize the operation system.
Enabling on Demand Deployment of Middleware Services
127
Besides componentization, our framework focuses on runtime component dependency management which contains dependency resolving and tracing, and so on. In our previous work, we have applied OSGi in the middleware to support service encapsulation and independent service deployment [13]. Based on the previous work, this study enhances the middleware with a runtime component dependency management and application driven deployment mechanism, as a result of which the runtime (un)deployment of services is controlled automatically by the middleware rather than manually by the administrator.
7 Conclusion The proliferation of middleware services and the specialization of application demands make the traditional way of deploying middleware services, which is that all the possible services are deployed at bootstrap, suffer criticism for the high resource consumption, shortage of application specific customization and low application development efficiency. We believe that, in the case of service deployment, the best way is to automatically and dynamically deploy the services which are specific to the application demands. Inspired by the ideas from component techniques, we proposed to support on demand service deployment by managing the dependencies between the middleware components at runtime. Moreover, we integrated the mechanisms that are necessary for on demand service deployment into a framework including management of middleware components, applications and the runtime dependencies among them. Finally, we implemented the framework in a Java EE middleware, and evaluated the performance improvement and overhead brought by the framework. Acknowledgments. We would like to give our thanks to the anonymous reviewers for their valuable feedback and to Xi Sun and the students in the application server group of Institute of Software, Peking University for their advices. This work is sponsored by the National Basic Research Program of China (973) under Grant No. 2005CB321805; the Science Fund for Creative Research Groups of China under Grant No. 60821003.
References 1. Zhang, C., Jacobsen, H.A.: Refactoring Middleware with Aspects. IEEE Transactions on Parallel and Distributed Systems 14(11), 1058–1073 (2003) 2. Colyer, A., Blair, G.: Managing Complexity in Middleware. In: 4th AOSD Workshop on Aspects, Components, and Patterns for Infrastructure Software (ACP4IS), pp. 21–26 (2003) 3. Kon, F., Campbell, R.H., Mickunas, M.D., Nahrstedt, K., Ballesteros, F.J.: 2k: A Distributed Operating System for Dynamic Heterogeneous Environments. In: 9th International Symposium on High-Performance Distributed Computing, pp. 201–208 (2000) 4. Syperski, C.: Component Software: Beyond Object-Oriented Programming. AddisonWesley, London (2002) 5. Clarke, M., Blair, G.S., Coulson, G., Parlavantzas, N.: An Efficient Component Model for the Construction of Adaptive Middleware. In: Guerraoui, R. (ed.) Middleware 2001. LNCS, vol. 2218, pp. 160–178. Springer, Heidelberg (2001)
128
Y. Li et al.
6. Leclercq, M., Quema, V., Stefani, J.B.: Dream: A Component Framework for Constructing Resource-Aware, Configurable Middleware. IEEE Distributed Systems Online 6(9), 1 (2005) 7. Fleury, M., Reverbel, F.: The Jboss Extensible Server. In: Endler, M., Schmidt, D.C. (eds.) Middleware 2003. LNCS, vol. 2672, pp. 344–373. Springer, Heidelberg (2003) 8. Hong, M., Gang, H.: Pkuas: An Architecture-Based Reflective Component Operating Platform. In: 10th IEEE International Workshop on Future Trends of Distributed Computing Systems (FTDCS), pp. 163–169. IEEE Press, New York (2004) 9. Li, Y., Zhou, M., Cao, D., Zhang, L.: Constructing Flexible Application Servers with Off-the-Shelf Middleware Services Integration Framework. In: Mei, H. (ed.) ICSR 2008. LNCS, vol. 5030, pp. 343–346. Springer, Heidelberg (2008) 10. Xiang, J.: Architecture-Based Deployment and Maintenance of J2EE Applications. Master Thesis, Peking University (2003) 11. Gosling, J., Joy, B., Steele, G., Bracha, G.: Java(TM) Language Specification, 3rd edn. Addison-Wesley Professional, Reading (2005) 12. Fowler, M.: Inversion of Control Containers and the Dependency Injection pattern (2004), http://martinfowler.com/articles/injection.html 13. Chao, Y., Minghui, Z., Zan, X., Hong, M.: Towards a Well Structured and Dynamic Application Server. In: 33rd Annual IEEE International Computer Software and Applications Conference, pp. 427–434. IEEE Press, Los Alamitos (2009) 14. Dearie, A.: Software Deployment, Past, Present and Future. In: Internation Conference on Software Engineering, Future of Software Engineering, pp. 269–284. IEEE Press, Washington (2007) 15. Ben-Shaul, I., Holder, O., Lavva, B.: Dynamic Adaptation and Deployment of Distributed Components in Hadas. IEEE Transactions on Software Engineering 27(9), 769–787 (2001) 16. Lan, L., Huang, G., Ma, L., Wang, M., Mei, H., Zhang, L., Chen, Y.: Architecture Based Deployment of Large-Scale Component Based Systems: The Tool and Principles. In: Heineman, G.T., Crnković, I., Schmidt, H.W., Stafford, J.A., Szyperski, C., Wallnau, K. (eds.) CBSE 2005. LNCS, vol. 3489, pp. 123–138. Springer, Heidelberg (2005) 17. Dearle, A., Kirby, G.N.C., McCarthy, A.J.: A Framework for Constraint-Based Development and Autonomic Management of Distributed Applications. In: 1st International Conference on Autonomic Computing, pp. 300–301. IEEE Press, Washington (2004) 18. Zhang, C., Gao, D., Jacobsen, H.-A.: Towards Just-in-Time Middleware Architectures. In: 4th International Conference on Aspect-Oriented Software Development, pp. 63–74. ACM Press, New Youk (2005) 19. Cervantes, H., Hall, R.S.: Autonomous Adaptation to Dynamic Availability Using a Service-Oriented Component Model. In: 26th International Conference on Software Engineering (ICSE), pp. 614–623. IEEE Press, Washington (2004) 20. Raatikainen, K.E.E.: Operating System Issues in Future End-User Systems. In: 16th IEEE International Symposium on Personal, Indoor and Mobile Radio Communications (PIMRC), pp. 2794–2800. IEEE Press, Los Alamitos (2005) (Invited paper) 21. Susmit, B.: Nano-kernel: A Dynamically Reconfigurable Kernel for WSN. In: 1st International Conference on MOBILe Wireless MiddleWARE, Operating Systems, and Applications, Innsbruck Tyrol, Austria, pp. 1–6 (2008) 22. Costa, P., Coulson, G., Mascolo, C., Mottola, L., Picco, G.P., Zachariadis, S.: Reconfigurable Component-based Middleware for Networked Embedded Systems. International Journal of Wireless Information Networks 14(2), 149–162 (2007) 23. ObjectWeb JORAM, http://joram.objectweb.org/ 24. Codehaus ActiveMQ, http://activemq.apache.org/
Enabling on Demand Deployment of Middleware Services
129
25. SONIC OpenJMS, http://openjms.sourceforge.net/ 26. JavaTM Platform, Enterprise Edition 5 Specification, http://jcp.org/en/jsr/detail?id=244 27. JO2nAS, http://wiki.jonas.ow2.org/xwiki/bin/ view/Main/Cooperation 28. OSGi R4 platform, http://www.osgi.org/Main/HomePage 29. Apache Felix Framework, http://felix.apache.org/site/ apache-felix-framework.html 30. OSGi Bundle Repositor, http://www.osgi.org/Repository/HomePage 31. Java EE 5 Tutorial, http://java.sun.com/javaee/5/docs/tutorial/doc/ 32. Geronimo, http://geronimo.apache.org/
A Self-healing Component Sandbox for Untrustworthy Third Party Code Execution Kiev Gama and Didier Donsez University of Grenoble, LIG, ADELE Team {kiev.gama,didier.donsez}@imag.fr
Abstract. This paper presents an architecture and implementation of a selfhealing sandbox for the execution of third party code dynamically loaded which may potentially put in risk application stability. By executing code in a fault contained sandbox, no faults are propagated to the trusted part of the application. The sandbox is monitored by a control loop that is able to predict and avoid known types of faults. If the sandbox crashes or hangs, it can be automatically recovered to normal activity without needing to stop the main application. A comparison between an implementation of the sandbox in a domain-based isolation and operating-system based isolation analyses performance overhead, memory footprint and sandbox reboot time in both approaches. The implementation has been tested in a simulation of an RFID and sensorbased application. Keywords: Fault containment, sandboxing, components, services, autonomic.
1 Introduction Component-based software development allows the construction of applications assembled from software components. Application development may likely involve the integration of different commercial off-the shelf (COTS) components, typically coming from a third-party vendor. This integration implies in testing how the different components will interact as well as trying to detect in advance any incompatibilities or application errors that may arise at runtime. If a component fails1 during execution, the whole composition that depends on it could fail, and depending on the failure, the whole application may also be down. Formal methods used in static code analysis are effective ways for testing and detecting errors in scenarios where components involved in a composition are known ahead of application execution. Indeed, there are drawbacks such as the size of software that such approaches are able to analyze (i.e. state explosions in larger software analysis) and the limited amount of people that master these techniques, which are not trivial. If components are not known ahead of execution, the task of integration testing becomes more difficult. Combinatorial explosions may be faced if we try to predict 1
A failure occurs when a delivered service deviates from correct service state. The deviation is called an error, and the hypothesized cause of such deviation is called a fault. [2].
L. Grunske, R. Reussner, and F. Plasil (Eds.): CBSE 2010, LNCS 6092, pp. 130–149, 2010. © Springer-Verlag Berlin Heidelberg 2010
A Self-healing Component Sandbox for Untrustworthy Third Party Code Execution
131
combinations by validating a component against all possible system configurations [1]. This is something very difficult to achieve in an open COTS market where new components are periodically released. Possible combinations still grow if other components can still be integrated after deployment of the system. The usage of COTS “as-is” has lead to more error-prone and less dependable2 applications, hence a recovery oriented approach [3] must be considered to achieve dependability. By acknowledging that hardware fails, that software has bugs and that human operators make mistakes, recovery oriented computing tries to reduce application recovery time (maintainability) thus increasing availability (directly influenced by maintainability), and consequently dependability. Another key factor for providing high availability is to modularize the system so modules can be the unit of failure and replacement [4]. By having well separated modules the application can give the impression of having instantaneous repair. Therefore, with a tiny mean time to repair (MTTR) the failure can be perceived as a delay instead of a failure. The Java platform dynamic class loading mechanism allows the development of frameworks like SOFA-DCUP [5] and OSGi [6] where components can be loaded at runtime. The OSGi platform is becoming the de facto dynamic module system for Java applications allowing to install, to start, to stop, to update and to uninstall components during application execution. A COTS market around OSGi is emerging [7] where third party components are becoming available increasingly, but evaluating their quality and trustworthiness is not a precise task. There are also issues due to the fact of components not being completely isolated from each other. Components that have been incorrectly developed may leave threads still executing or perhaps can still have their objects erroneously referenced after component uninstallation. Component uninstallation is possible in OSGi, but since they are not actually purged from memory these error scenarios are likely to exist [8]. In the long run, applications may accumulate inconsistencies due to dynamicity mishandling. Fault tolerance and containment are useful for systems that may face unanticipated events at runtime that are difficult or impossible to test during development [9]. By establishing barriers for containment, we can minimize failures impact in the application. If a new component deployed into the system introduces a problem, the application should not stop working. Although sources of errors due to direct memory allocation and handling pointer variables are not present in the Java platform, applications are not free of memory leaks neither completely exempt of other types of faults that may crash or hang the application. Code of poor quality or not exhaustively tested, resource consuming code, and component incompatibilities, among others reasons, may bring a program down or significantly degrade application performance and responsiveness. Importing or wrapping native libraries (e.g., a device driver) in Java applications (and .NET as well) also increases the risk of an eventual crash. This is one of the motivations of our work in the EU funded Aspire project3. In one of the scenarios we have an application that collects RFID and sensor data for building reports, eventually using native drivers wrapped as Java components for accessing the sensors 2
Dependability, which encompasses some primary attributes like availability, safety, integrity and maintainability, is defined as the ability to avoid service failures that are more frequent and severe than acceptable [2]. 3 http://www.fp7-aspire.eu/
132
K. Gama and D. Donsez
and RFID readers. Sensors and drivers may be plugged at runtime. The reporting application must run non-stop and be able to recover in case of severe faults. An important point is to provide mechanisms to avoid the propagation of faults from one component to another, so the system can still execute even if one of its components crash. The identification of the faulty component is also an important issue. In the same way, there is a need to automatically react to possible faults and reestablish normal system behavior/execution upon component faults. Among our motivations is also the possibility of enabling the execution of untrustworthy (but not necessarily malicious) third party code without compromising application stability. An application’s core functionality must be separated from untrustworthy third party code (native or not). Minimizing the possibility of error propagation also minimizes application disruption. Microreboots [10] for performing a full reset on an isolated component can actually purge faulty components from memory and bring them back without needing to reset the whole application. In our precedent work [11] we have enumerated the different approaches for component isolation in the Java platform (namespace based, domain based and process based). By using domain based isolation we have presented a proof of concept that provided fault containment by means of a sandbox for the execution of untrustworthy components. In case of severe failures of components in the sandbox, the main application was not disturbed. In the current paper we extend our previous work by adding the desired automatic reaction to faults and failures. We have introduced autonomic computing principles to our sandboxing architecture, allowing self-healing based on fault prediction (e.g., the application state is not adequate to the application’s policy) and fault handling (e.g., recovering from a fault). The paper presented herein has two objectives: − To perform a comparison of the cost (memory footprint, communication overhead) of the sandboxing approach using two different component isolation approaches, namely domain based and O.S. based (i.e. separate processes). − To detail the sandboxing architecture autonomic aspects and implementation. The architecture and its implementation were validated in a controlled experiment of an application that deploys different components in a sensor based application. Although the solution presented here is directly linked with OSGi technology, some of the risks that we point out exist in most of the centralized component based systems. In addition, the self-healing sandbox principle is useful for other dynamic component technologies. The rest of this paper is organized as follows: section 2 gives some motivations and background; section 3 details the architecture and the implementation; section 4 details the experiments and their results. Section 5 presents related work, and section 6 presents conclusions and perspectives of our work.
2 Motivations and Background The OSGi Service Platform keeps gaining popularity in the development of modular Java applications, with a COTS market [7] that keeps growing. The next subsections provide some background on OSGi and the limitations we try to address, followed by some background on the techniques employed in our solution.
A Self-healing Component Sandbox for Untrustworthy Third Party Code Execution
133
2.1 OSGi and Dynamicity The OSGi Alliance specifies4 a framework that allows the dynamic deployment and undeployment of components and services. Applications can take advantage of Java’s dynamic class loading feature for updating software components without the need to stop the application. For example, a production system may have a component updated with a new version due to minor bugs fixed or other types of improvements without needing to stop the application for the update. The deployment unit in OSGi is called bundle, which is an ordinary compressed jar file which contains classes and resources. The jar file manifest contains OSGi specific attributes describing the bundle, which provide metadata and the bundle dependencies (e.g., a list of imported and exported class packages). A bundle can be dynamically loaded or unloaded on the OSGi framework and may optionally provide or consume services, which can be any Java object. Services need to be registered in the OSGi service registry as providers of the specified interface or interfaces. A bundle needs to have its type dependencies resolved before it can be started. In fact, there are two levels of dependencies: package dependencies (types), and service dependencies (objects). The former are dynamically resolved and necessary for bundle activation, while the latter are also dynamically resolved but not necessary for bundle activation. Compared to the intercomponent dependence types proposed by [12], they can be seen as prerequisites and dynamic dependencies, respectively. Optionally, an OSGi bundle can provide an Activator class, specified in the manifest, that is instantiated and called when the bundle is started. At that moment of startup the activator code can spawn threads and register services in the OSGi service registry. The actual dynamic composition mechanisms rely in a service-oriented composition approach. OSGi uses service-oriented principles for providing strong decoupling between components. In a Service Oriented Architecture (SOA) there are the service provider, service requestor and service catalog which in the OSGi framework take the form of a bundle that provides a service, a bundle that requests a service, and the OSGi service registry, respectively. Different service-based component models (e.g., Declarative Services, iPOJO) have been constructed as layers on top of the OSGi service registry helping to manage the complexity and to minimize the burden of service registration/unregistration that govern the service dependencies and bindings. However, such models are not enough for guaranteeing the mishandling of references. Stale references [8] are a typical problem in OSGi applications when the dynamicity is mishandled. It is characterized by objects provided by the classloader of a bundle that has been stopped or uninstalled; and by references to services that are no longer available, shown in Fig. 1. The dynamic replacement of components is not complete since we cannot purge the components from memory. This happens, in part, due to the fact that the in OSGi objects have just namespace isolation. This limitation in isolation may also have consequences if a component crashes (e.g., due to an unstable wrapped native driver) the application. In fact, such risk is common to most centralized component based applications that share the same memory space. 4
Different open source implementations exist such as Apache Felix, Equinox and Knopflerfish.
134
K. Gama and D. Donsez
Bundle X
Bundle Y
Bundle Y’
OSGi OSGi Active bundle (i.e. started) Inactive bundle (e.g. stopped, uninstalled)
Fig. 1. A stale reference example. Bundle Y has been replaced by Bundle Y’, however a service from Bundle X keeps referencing an object from an old version of Bundle Y.
2.2 Self-management Some recent efforts from different research communities in computer science are motivated by the goal of developing applications that can minimize human intervention for system maintenance. One of the motivations to attain is a reduction of costs concerning installation, configuration, tune up and maintenance of software applications. Usually under the self-* flag (self-adaptive, self-healing, etc) these efforts try to provide systems that work autonomously with no human intervention. IBM has coined the term autonomic computing, inspired by the autonomic nervous system, for describing systems that are self-manageable. According to the vision shared by IBM, the four main aspects of autonomic-computing are [13]: − Self-configuration. Based on high-level policies, the system transparently reacts to internal or external events and adjusts its own configuration automatically. − Self-optimization. The system is able to improve continuously its performance. − Self-healing. Automatic detection, diagnosis and repair of software and hardware problems. − Self-protection. Automatic anticipation and reaction of system wide failures due to malicious attacks or cascading failures which were not self-healed. A managed element or managed resource consists of hardware (e.g., a processor, an electronic device) or software (e.g., a component, a subsystem, a remote service). A managed element exposes manageability endpoints (or touchpoints) which provide sensors and effectors [13]. The sensors provide data (e.g., memory consumption, current load) from the element and the effectors allow performing operations such as reconfiguring. An autonomic element consists of one or more managed elements controlled by an autonomic manager that accesses the managed elements via their touchpoints. Autonomic managers fall into the four above mentioned self-* categories. Such managers are implemented using an intelligent control loop. IBM proposes a MAPE-K (Monitor, Analyze, Plan, Execute, Knowledge) control loop (Figure 2) model which is taken as a one of the main references for autonomic
A Self-healing Component Sandbox for Untrustworthy Third Party Code Execution
Autonomic Manager
Control
Analyze
Decision
Resource
Measure
135
Monitor
Plan
Knowledge
Execute
Touchpoints Sensors
Effectors
Managed Resource (a) A Generic Control Loop
(b) The control loop proposed by IBM
Fig. 2. Illustration of the control loop principle (a) and the MAPE-K loop proposed by IBM for autonomic elements (b). Figure adapted from [14] and [15], respectively.
control loops. Basically, the control loop monitors data (e.g., the inspection of system performance or current state) from a managed element; interprets them verifying if any changes need to be made; if it is the case, the action needed is planned and executed by accessing the managed element’s effectors. Knowledge is a representation of live system information (e.g., an architectural model, reified entities) that may be used and updated by any of the MAPE components, thus influencing decision taking. An autonomic manager can also have just portions of its control loop automated [15]. Functionalities that are potentially automated could also be under manual supervision (e.g., decision taking upon certain events) of IT professionals. The administrators are also responsible for configuration, which can ideally [16] be done by means of high-level goals, which are usually expressed by means of event-condition-action (ECA) policies, goal policies or utility function policies. 2.3 Microreboots as a Self-recovery Approach A self-healing system must be able to recover from a failed component by detecting and isolating the failed component, taking it off line, fixing or isolating it, and reintroducing the fixed or replacement component into service without any apparent application disruption [14]. Several studies suggest that many failures can be recovered by rebooting, even when their cause is not known [10]. Normally, hard to identify faults could be caused by diverse sources difficult to track such as race conditions, resource leaks or intermittent hardware error, and reboots are the only solution for reestablishing correct application execution and bring the system back to an acceptable state [17]. This is often the case of day-to-day embedded systems in devices like portable phones or ADSL modems as well as server and desktop applications that may present faults which disappear after rebooting. In [18], the authors show evidence that a significant amount of software errors are a consequence of peak conditions in workload, exception handling and timing. Such errors typically disappear upon software re-execution after clean-up and re-initialization.
136
K. Gama and D. Donsez
The microreboot technique [17] proposes the individual reboot of fine-grained components, achieving the same benefits of an application restart but at lower costs and without losing application availability. OSGi allows microreboots in individual components by performing calls to the stop and start methods, or even an update, which refreshes a component. However, as previously pointed out, the restart of individual components in OSGi may still leave state inconsistencies. A restart of the whole application would be necessary to reestablish its correct state. However a complete shutdown of the application is not desired especially in applications that need to provide high availability. We are mostly concerned with third party code that would be dynamically loaded and has not been previously tested with the application. Potential faults or errors in the third party code could be fixed by simple microreboots, and if they persist, a full reboot by purging the component out of memory and bringing it back would restore its correct state and behavior. Our goal is to put the third party code in a sandbox which would work as a separate fault contained environment that is independently rebootable. Such isolated code running is a separate boundary would still be able to communicate with the trusted part of the application and vice-versa. These boundaries are fault contained in the sense that crashes or misfunctioning would not be propagated from one boundary to another.
3 Architecture and Implementation In our precedent work we have provided a proof of concept that validated the isolation approach of our sandbox. However, even if sandbox crashes would do no harm to the trusted application, the sandbox still needed a manual restart by the application administrator. As we consider the sandbox an important point for executing untrustworthy code, we have improved its architecture so it could be constantly monitored, and be able to predict potential faults, as well as recovering itself from crashes or other failures. Our motivation was to provide the sandbox as an autonomic element. An ideal scenario would be having one fault containment boundary per component, but that is an expensive solution in terms of memory footprint with current Java technology. Our approach uses one sandbox only, and one “trustworthy” platform. While the isolated sandbox is being restarted, the system can still provide its functionality in a degraded mode. The next section provides details on the architecture and implementation of the autonomic approach introduced for the sandbox. 3.1 Architecture An overview of the architecture is depicted in Figure 3, which presents a component diagram containing the main elements for realizing the self-healing sandbox. Each component presented in the diagram is detailed next. Trusted Platform. The Core and Service Registry correspond to components that are part of the OSGi implementation, which we had to change by adding the code for enabling the sandbox approach, while the other components detailed in Figure 3 concern the improvements that we implement.
A Self-healing Component Sandbox for Untrustworthy Third Party Code Execution
137
Sandbox Platform
<<delegate>>
<<use>> Core
<<delegate>> <<use>>
PlatformProxy
<<use>>
<<use>>
Isolation Policy Eval.
<<use>>
Core
Trusted Platform
<<use>> Service Registry
PlatformProxy <<use>>
<<delegate>> <<use>>
<<delegate>> <<use>> Monitoring MBean
Service Registry
EffectorMBean
<<delegate>> <<delegate>>
<<use>>
HeartbeatProbe
<<delegate>>
SensorProbe
EffectorProbe
Autonomic Manager <<delegate>>
<<delegate>>
Watchdog
<<delegate>>
Monitor <<use>>
<<use>> <<use>>
Policy Evaluator
Strategy Executor <<use>>
<<use>> <<use>>
Knowledge
<<use>>
<<use>> Script Interpreter
Fig. 3. Component diagram illustrating the interactions of the main components of the trusted platform, sandbox platform and the sandbox autonomic manager. The grey ones are parts of the OSGi framework that were changed, while the white ones are part of our architecture.
Core. The main change in this component concerned bundle life cycle operations (install, start, stop, update, uninstall). The core component exists in both platform, but the aforementioned changes apply only to the trusted platform. With the code that has been added, upon an OSGi bundle installation the core installs that bundle in the trusted platform and also in the sandbox so the same dependencies are present in both platforms. In our initial approach we installed only the necessary dependencies, but the dependency resolution we provided calculated only one level (for A → B, install A and also B). Since dependency transitivity could lead to several levels (A → B → C) and possibly cycles, we decided to simplify and replicate all components and perform also uninstall and update operations, keeping the bundle set synchronized in both platforms. In OSGi, a component installed is not necessarily loaded in memory, therefore it would not imply in loading all bundles in the sandbox container. Before starting up a bundle, the core component verifies with the isolation policy evaluator if the component can be installed in the main platform or if it needs to be installed in the sandbox. Isolation Policy Evaluator. This component exists only in the trusted platform. It is responsible for verifying the policies for services and component isolation. Two files describe the respective isolation levels to be attained. The service isolation is used inside the main platform, by adding a proxy layer between service consumer and provider [19]. The component isolation policy provides the rules that indicate which
138
K. Gama and D. Donsez
components need to be started in the sandbox. Both files are defined using a simple Domain Specific Language. Service Registry. This was the other existing component that was changed in order to make the sandboxing work. Upon calls to the getServiceReference method, if no match is found in the registry, the call is forwarded (via the PlatformProxy) to the sandbox. If the service instance is located in the sandbox, an IsolatedServiceReference instance is provided to the caller. For services local to the trusted platform, one extra step is performed by verifying with the Isolation Policy Evaluator if the service instance needs to be proxied, so the service provider is not directly referenced by the consumer code. PlatformProxy. This component provides a communication layer between the two platforms. Method calls are translated into the appropriate protocol messages so the main platform can perform life cycle operations (install, update, start, stop, uninstall) in isolated bundles; execute queries looking for services in the sandbox service registry; as well as execute the method calls on isolated service instances. In order to avoid services to implement interfaces like java.rmi.Remote, so the isolation could be transparent, the protocol used in this layer was custom made. Sandbox Platform. The sandbox has components that are also present in the trusted platform. Additional components are available, as depicted below, so the control loop of the sandbox autonomic manager can be realized. Core. In the sandbox platform side, the role of the core component is rather passive. When the sandbox’s PlatformProxy receives messages concerning bundle life-cycle operations, the calls are delegated to the core component, which does not need any special code or any sort of customization for executing such operations. Service Registry. The service registry works just like the trusted platform: if no service match is found locally for a service search, the call is forwarded to the trusted platform using the PlatformProxy. For both platforms, a flag is set on the service queries received in order to avoid infinite cycles when no service query is satisfied in any of the two platforms. The additional functionality that exists in the sandbox service registry relates mainly to two points concerning the proxies that point to services of the trusted platform. The first point is the logging of service calls so it is possible to identify the number of calls per second on a service, and the second point concerns the invalidation of proxies. If the sandbox has a proxy to a service running in the trusted platform and that service becomes unregistered, the sandbox is notified and the proxy invalidated. By doing this it is possible to throw an exception and identify if a component (and which one) hosted in the sandbox is using a stale service. PlatformProxy. This component works in the same way as it does in the trusted platform. One difference lies only in its usage: there are no bundle life cycle calls performed. The calls are rather received — since it is the trusted platform that will control the installation of bundles in the sandbox — processed and their response is sent back to the trusted platform. The communication initiated on the sandbox towards the trusted platform only concerns service related calls (service registry queries, service method calls and service registration/unregistration events).
A Self-healing Component Sandbox for Untrustworthy Third Party Code Execution
139
MonitoringMBean. This component is implemented on top of Java Management Extensions (JMX)5, which is a technology specialized for monitoring Java applications. The Monitoring MBean (Manageable Bean) defined by our architecture can be easily accessed by other Java processes. It provides information concerning CPU consumption, memory usage, number of allocated threads, list of bundles, list of proxied services, service calls per minute (per service basis), stale service count and potential bundles that are retainers of a stale service. EffectorMBean. Using the same technology as the Monitoring MBean, the effector component makes available a set of operations on the sandbox platform so the autonomic manager can be able to reconfigure it and adapt the application at runtime. Through its interface it is possible to stop the framework (graceful shutdown), to kill the platform, to perform a garbage collection, to invalidate a given service proxy, to stop and to start a given bundle. Sandbox Autonomic Manager. Our approach is based on the MAPE-K control loop with some simplification by unifying the Analysis and Planning into one component (Policy Evaluator). The autonomic manager is maintained as a separate process that controls the sandbox. Therefore, problems in the sandbox will not interfere in the functioning of its autonomic manager. Its main components are described next. Watchdog. The watchdog component is responsible for restarting the sandbox platform in case the process is crashed or hung. A process is crashed if its image is no longer in the system, and it is hung if the process image is alive, but the process is not making any progress from a user's point of view [18]. Heartbeat messages are periodically sent to the JVM process and depending on the time taken for the response (or no response) it can be inferred that the process is hung and then the autonomic manager can restart it. If a sudden crash also happens, the watchdog can recover the process and reestablish the connections as well as restart the control loop. The watchdog relies on the java.lang.Process API for starting up the sandbox as well as for killing it. The instantiation of the monitor component is made right after the sandbox is launched or restarted. Monitor. If compared to the watchdog, the monitor component does a much more specialized monitoring. It plays a role in the control loop for collecting information from the managed element (i.e., the sandbox platform), saving pertinent information in the knowledge base and delegating the monitored values to the policy evaluator component. Different types of monitoring readings are done either in push mode (e.g., event for call on invalidated proxy) or in a periodic poll (e.g., memory, CPU, threads, stale service count). When the monitor starts up it logs that information in the knowledge base so it can analyze later how many and how frequently startups have been done. Knowledge. The knowledge component stores some historical events for later analysis of the other components. It store information such as events of sandbox reboot and reason; lists of offender bundles; trespassing of thresholds (e.g.: memory, CPU). By using such information it is possible to know, for example, if the reboots are too frequent and depending on the custom policy, to change the set of active components in the sandbox based on other logged information. 5
http://java.sun.com/jmx
140
K. Gama and D. Donsez
Policy Evaluator. The policy evaluator makes use of the scripting engine to interpret the policies. The monitored data is made available to the scripting execution context, so the policy can have access to the current values. If no action is to be taken, the current loop iteration ends. In case of action to be taken, the name of the strategy is passed to the strategy executor. Strategy Executor. The strategy executor is responsible to load the strategy file, instantiate the scripting engine and execute the strategy. During this process the strategy may gather information (e.g., which bundles are the potential retainers of a stale service) from the knowledge component in order to take a decision. The code outside the application as beanshell scripts gives flexibility and leaves the possibility of customizing the behavior without needing to recompile code. Script Interpreter. The script interpreter allows the instantiation of a scripting engine which can be used by both the policy and strategy evaluators. The current implementation uses scripts written in Java, interpreted by beanshell6. Integration to another Java standardized scripting engine, like Rhino, is transparent to the other components but would require rewriting the policies and the strategies in the target scripting language. 3.2 Domain-Based and OS-Based Implementations The proof of concept was implemented using domain-based isolation provided by the Java Isolation API (Java Specification Request-121)7. We have used the Multitasking Virtual Machine [20] (MVM) from SunLabs that implements the Isolation API. The JSR-121 presents the notion of Isolates which are a first class representation of a strong isolation container with an API to control their lifecycle. The implementation of the sandbox consisted of patching the Apache Felix v.1.4 OSGi implementation. It was made on top of Open Solaris (release 2008.11) using an x86 port of the MVM8. Some reasons have motivated us to also implement the sandbox using OS-based component isolation. Although standardized as a JSR, the Isolate API is usually available in Sun Microsystems products which normally have restricted access. The MVM version we have used was not a final product, and also presented instability. JMX functionality did not provide resource monitoring at the Isolate level (e.g., we could not differentiate the memory consumption of the sandbox platform from the trusted platform). It was one of the forces that lead us to implementing the sandbox approach using two different VMs (OS-based isolation) replacing the two Isolates as fault contained entities. This would be a solution reusable in any standard JVM. Due to JMX limitations on the MVM we could not enable the full autonomic manager using Isolates. The domain-based version of the sandbox only had a stripped down version of the watchdog which directly monitored the connection between the two Isolates (trusted platform and sandbox platform). The trusted and sandbox platforms needed to exchange messages in an Inter-Process Communication (IPC) fashion. Existing protocols for Java IPC (e.g., RMI, Hessian) rely on extending classes and implementing marker interfaces of such APIs. In order to enable an object to be used with RMI for example, an object must implement an 6
Beanshell Lightweight Scripting for Java, http://www.beanshell.org/ Application Isolation API Specification, http://jcp.org/en/jsr/detail?id=121 8 http://mvm.dev.java.net 7
A Self-healing Component Sandbox for Untrustworthy Third Party Code Execution
141
interface that extends the java.rmi.Remote and all methods must throw a java.rmi.RemoteException. Since we wanted to transparently enable the sandbox approach, we have implemented a simple protocol for communicating the trusted platform with the sandbox and vice-versa without forcing the source code or bytecode of service objects to be changed. The communication layer on the MVM was made using the Link API which allows two isolates to exchange messages. The multiple JVM approach reused the same protocol but with sockets instead of the link API. Assumptions. In order to work properly, our approach is realized based on a set of assumptions: − The set of components that coexist in the trusted platform has already been tested and has a minimal probability of bugs. − Based on one of the microreboot conditions [10], services that will run on the sandbox are stateless otherwise they may have state corruption in case of reboots. − The communication between platforms will be done through services with simple interfaces (String and primitive values as well as arrays of those types). Limitations. The solution also has some limitations concerning the functioning of OSGi applications. − Inter-platform protocol limitation (pass by copy, and method calls using primitive values only), thus restricting the set of services that could exchange messages across isolated platforms. − No fine grained bundle resource monitoring (needed for a precise monitoring on the autonomic manager). Anyway, it does not exist in regular OSGi frameworks. − No isolated bundle referencing (e.g., getBundle method will not work on isolated service notifications or on an isolated service reference) Microreboot Behavior and Effects − Components running in the sandbox are actually purged from memory, since the sandbox platform goes through a shutdown and its isolated container (JVM process or Java Isolate, depending on the approach) is restarted. − The state of service instances is not maintained (services are stateless as previously assumed). Service providers may use their own mechanisms for that. − The state of the sandboxed bundles (e.g., started, stopped) is managed by the OSGi platform and it is maintained across reboots. − The disruption of the communication between the main application and the sandbox causes the communication layer to generate events notifying the departure of the services registered in the sandbox. − Upon sandbox shutdown, ongoing calls from the main platform towards the sandbox are not able to be completed. No calls remain blocked at all. In that case, the communication layer throws RuntimeExceptions for each waiting call. A possible solution would be a strategy for holding the call in the communication proxy and wait for the sandbox restart until the required service interface provider becomes available again and then retry the service call.
142
K. Gama and D. Donsez
4 Evaluation This section is divided in two parts: 1) a comparison between the sandboxing mechanism using two isolation approaches, namely domain isolation and OS-based isolation; 2) the tests on the sandbox with self-healing characteristics, using the OS-based approach. The experiments were executed on a Pentium 1.7 GHz 2GB RAM running OpenSolaris release 2008.11. Three different Java Virtual Machines were used: Multitask Virtual Machine (a JVM 1.5 implementation that provides the Isolate API); Sun HotSpot Server Virtual Machine versions 1.5.0_21 and 1.6.0_10. Except for the microbenchmark, all experiments were performed in a simulation of an OSGi application for collecting RFID and sensor data with a total of 14 bundles. Sensors and RFID reader simulator components were hosted in the sandbox. 4.1 Domain-Based versus OS-Based Isolation This subsection compares the two approaches in order to verify what would be the gains, if any, of using domain-based isolation. The following aspects were verified: − The overhead of method calls across isolation boundaries. − The memory footprint of OSGi applications using our isolated sandbox − Sandbox microreboot time The first measurement consisted on evaluation the communication overhead between the isolated platforms. On the MVM, we have evaluated it in two ways. On the first way, trusted and sandbox platforms were running in the same VM but in different Isolates, thus having domain-isolation. On the second one, we have used two MVM instances like an ordinary JVM (i.e. not using Isolates) so we could use the whole process as a fault-contained boundary, providing us OS-based isolation. We have used the benchmark suite used in [21] with slight adaptations. The current microbenchmark consisted in measuring the time taken to perform method call from the trusted platform to a service which is isolated in the sandbox. Three methods with different signatures were evaluated: a parameterless method; a method with a String parameter; and a method with an integer array with 128 elements so we could see the impact of parameter serialization and deserialization. All methods were void, so not returning any value. Since RMI is the standard Java Inter-Process protocol, we have benchmarked our approach against it. Table 1 presents the result of our microbenchmark. The experiment data had acceptable precision since each set of measured data had a coefficient of variation (ratio of the standard deviation to the mean) inferior to 1% in most of the cases and rarely over 1%. The results on the Custom Protocol column group concern the calls on the isolated service running in the sandbox as previously described. The RMI column group results actually did not execute in an OSGi application. We have taken the same interface as the tested service and changed its code to add what was necessary to enable RMI. Then it was tested on two non-OSGi applications (an RMI client and a server, respectively) coded exclusively for the benchmark. The usage of RMI in a non-OSGi application which used 35% less threads than the OSGi application also gives RMI a slight advantage. But it would still be more performing since our protocol was 2 to 3 times slower. Our protocol uses dynamic Java proxies in both ends, which is likely one reason for its low performance comparing to local RMI.
A Self-healing Component Sandbox for Untrustworthy Third Party Code Execution
143
Table 1. Microbenchmark in microseconds (μs) on a void method m with different signatures between isolated platforms. The custom protocol on an OSGi application was benchmarked againt local RMI calls in a non-OSGi application for comparative purposes.
Custom Protocol (Sandboxed OSGi application) Combination
m( )
MVM (2 Isol.) 2 x MVM 1.5 2 x JVM 1.5 2 x JVM 1.6
178.72 182.74 162.58 129.12
m(String)
m (int[128])
225.22 231.23 203.71 161.49
277.56 284.49 241.39 190.67
Local Java RMI (non-OSGi application) m( )
75.68 82.19 63.58 53.46
M(String)
m (int[128])
80.93 87.62 67.40 55.24
103.36 110.33 87.14 66.83
The usage of domain-based isolation concerns only the first result line. The second result line also uses the MVM but in an OS-based fashion. We can notice that two MVM Isolates (domain isolation) perform slightly better than using two MVM instances (OS-based isolation). This is due to the fact of a faster context switching since the Isolates run in the same process (the JVM instance). The third and second result lines performed slightly better which is most likely due to JVM optimizations since they are more recent versions. If running with the JVM configured as interpreted mode (-Xint option), without JIT optimizations, the performance reduction was relatively similar in all cases ranging from 3 to 6 times slower than in the optimized mode (-server option), which is the mode used for collecting the results. Another comparison we have performed concerned memory footprint, as shown in Figure 4. We have used the Solaris pmap command for verifying the resident and private memory of the tested combinations. The experiment consisted of measuring the total footprint of the OSGi test application (trusted platform + sandbox platform). In the OS-based approach used with two JVMs 1.5 and two JVMs 1.6 we have added the footprint of each JVM. In the case of domain-based approach a single MVM instance contained both OSGi platforms. The resident memory of the MVM running two isolates was inferior to the sum of sandbox and trusted platform running on the JVM 1.5. However, the two JVM 1.6 together performed with less footprint. If we consider just private memory the MVM performs better than the other ones. The third and last comparison made consisted on the time taken to perform application startup and a sandbox microreboot. Although we did not use a full autonomic manager on the domain-based approach for this experiment, we could provide a watchdog that is able to restart the sandbox in case of crashes. Table 2 presents the time taken in each VM combination. By using Isolates we can significantly reduce the mean time to repair of the sandbox. The major difference is probably because the watchdog monitors directly the Link objects that are responsible for the communication of the two platforms. Since the watchdog resides in the same process, the crash detection is immediate upon the disruption of the Link object. Based on these experiments we can verify that the main advantage of using domain-based isolation over an OS-based isolation implementation of our sandbox
144
K. Gama and D. Donsez
90
Single JVM (Domain-based)
80
Sandbox
70
Trusted platform
60 50 MB 40 30 20 10 0 MVM (2 Isolates)
2 x JVM 1.5
2 x JVM 1.6
Virtual Machines Combination
Fig. 4. Resident memory footprint of sandbox solution using different VM combinations. The one on the left uses domain-based isolation while the other two use OS-based isolation. Table 2. Average startup time and sandbox reboot time in milliseconds
Combination
MVM (2 Isolates) 2 x MVM 1.5 2 x JVM 1.5 2 x JVM 1.6
Application Startup time (ms)
Sandbox Crash detection time (ms)
Sandbox Reboot time (ms)
3186 3449 3945 3859
32 697 660 658
303 3064 3047 2537
approach concerns the application startup time and, especially, sandbox microreboot time. The memory footprint (resident memory) differences were not very significant, at least for the evaluated application. Communication overhead across process boundaries is minimized in more recent and optimized JVM versions. Therefore, an OS-based approach seems to be a reasonable option for the realization of the sandbox. 4.2 Autonomic Manager Validation The validation of the autonomic manager using the OS-based approach consisted in simulating scenarios where the addressed errors would occur. It was necessary to use a technique for fault injection. The behavior of systems tested with faults injected in the interface level (e.g., passing invalid parameters) significantly differs when faults are injected in the component level (e.g. emulation of internal component errors), not representing actual application usage [22]. For testing the autonomic manager, we rather focused on test cases resembling component fault injection that could reflect possible faults in a realistic scenario. In our case, the term fault deployment would be more appropriate, since the dynamic platform allows components to be deployed and started at runtime.
A Self-healing Component Sandbox for Untrustworthy Third Party Code Execution
145
The faulty components introduced faults for overutilization of CPU; excessive memory allocation; excessive thread instantiation; excessive invocation of services (Denial of Service); stale reference retainer and application crash. Observations on the system lead to findings such as the CPU information retrieved via the Java API not reflecting actual CPU usage verified via Solaris’ top command. Also, in cases of overutilization of memory the watchdog would kill the sandbox before the memory rule could take action. This would happen in occasions where the watchdog is configured with a low value for unresponsiveness time. The fault prediction approach we have used is rather pragmatic, and implemented as policy scripts interpreted by the policy evaluator component. The case of stale reference retainers is the one that involves more details on implementing the policy, being far from trivial. The scripts for fault detection concerning the other deviating states are straightforward and can be directly applied after reading the monitored data in most cases, needing to extract minor information from the knowledge component. The faulty bundles utilized were yet simplistic, and more robust validation is needed. The microreboots triggered by the autonomic manager would restore the sandbox to its initial state, since a restart is performed. As previously described in our assumptions, we consider that services are stateless so the microreboot strategy can work without corrupting service state. Except if we replace a faulty component by another one which provides a correction for the detected fault, a microreboot cannot guarantee to permanently remove a fault. However, considering the software rejuvenation approach [23], which is one of the influences on the microreboot technique, intermittent errors are likely to disappear after an application or component restart (in this case, a sandbox restart). Due to limitations on resource monitoring at the component (bundle) level, it is difficult to identify the bundle that is the source of some errors such as memory consumption. In cases where a component that causes abnormal execution is identified (e.g., stale reference retainer), it is possible (though not yet implemented), to provide reactive code to indentify the misbehaving component with the help of information stored on the knowledge component and then stop it. Like so, replacing the component with an appropriate or alternative version would also be possible if implementing a search mechanism accessing and using metadata of OSGi Bundle Repositories (OBR9), which are federated bundle repositories described by XML files. In order to have such bundle replacement mechanism one would need to query an OBR using its capability metadata (e.g., metadata about provided packages and services) as a filter for finding a bundle that would provide the same services as the faulty bundle does.
5 Related Work A survey on self-healing systems [24] presents several considerations and existing approaches for developing such types of systems. It highlights different strategies for maintaining system health. Among those strategies we have find in our techniques: system maintaining by probing in a feedback control loop; isolation of faulty components; performance monitoring; prediction of events; and system rejuvenation. The technique of having a watchdog process [18] for monitoring if an application hung or 9
OSGi standard based on the OSCAR Bundle Repository (http://oscar-osgi.sourceforge.net/).
146
K. Gama and D. Donsez
crashed is also proposed as a fault tolerant technique [25] for third-party components, as well as the microreboot technique. The Rainbow [27] framework uses software architectures to perform the selfadaptation of systems. Rainbow’s control loop uses an abstract architectural model to monitor a system’s runtime properties. If any constraint violation is detected in the architecture model, the control loop performs global-level and module-level adaptations on the running system. The JADE framework [26] uses an architecture-based approach for autonomic distributed applications, specifically JavaEE clusters. Among other employed techniques, JADE uses a component-based design on top of the Fractal component model, a control loop for monitoring the managed elements, and system replication for fault tolerance. Failed nodes are detected using a heartbeat technique. Its architecture-based approach is realized by means of a representation of the managed system (system representation) that is synchronized with the running system. Changes on the representation are mirrored to the system and vice-versa. Different efforts can be found [28, 29] using the OSGi platform targeting autonomic computing, but they are usually focused on the dynamicity and flexibility of the platform, not addressing dependability aspects. We can find an experimental solution [30] that also performs custom adaptations in the Apache Felix OSGi implementation. They try to introduce into the OSGi framework some autonomic computing concepts, also based on control loops. However, the work focuses on self-configuration, not mentioning any strategies for handling faults or application crashes. There are some assumptions concerning resource usage that are true only in limited scenarios. One of the perspectives of the work is to employ more efficient isolation techniques. An adaptive fault management approach targeting the Robocop framework [31, 32] concentrates efforts on mechanisms for fault detection and failure repair. They use service instances wrappers for intercepting calls to the underlying service and perform model-based verifications on the incoming calls in order to check if there is any deviation from the service specification. If it is the case, then the appropriate repair action is taken using the adequate repair rule. This mechanism works in an adaptive fashion: a sort of knowledge base stores the repair rules taken for given services. Future repairs may use that information to choose the best repair action for a given case, or generate a new repair rule if the stored ones are not appropriate. Concerning other isolation mechanisms related to ours, we also can cite Microsoft technologies such as COM (Component Object Model) components which can be either loaded in the client application process or provided in an isolated process [33]. In the latter case, a surrogate process (dllhost.exe) can load the DLL and act as a server. It enables fault isolation between the client and the component server, but the inter-process communication between them introduces a performance overhead. A similar approach can be done in .NET platform, the successor to COM technology, by using Application Domains. .NET allow the dynamic loading of classes similar to the Java technology, but the unloading remains limited [34] thus restricting the usage of .NET for the creation of a dynamic platform such as OSGi. The .NET framework 4.0, provides the Managed Add-In Framework (MAF) [35] which is a programming model allowing to create and to host add-ins, typically third party code that needs to be used without compromising the host application stability. To achieve that, the MAF allows an add-in to be hosted in a separate Application Domain or in a separate process. A MAF’s architecture comprises a pipeline of seven
A Self-healing Component Sandbox for Untrustworthy Third Party Code Execution
147
assemblies (Host, Host View, Host Adapter, Contract, Add-in Adapter, Add-in View, Add-in) which need to be provided if an add-in is to be used. Although they provide a robust approach with isolation in mind for loading and using third-party code, realizing managed add-ins is overly complex considering the number of assemblies to be provided and maintained if compared to the transparent approach we propose. We use very similar principles to those of R-OSGi [36], which proposes a protocol for communicating remote OSGi services. It uses dynamic bytecode generation for service consumer proxy code which is loaded as a bundle into the client platform, which significantly increases the number of executing bundles. Their motivation is also to provide a transparent communication mechanism but with distribution in mind, however R-OSGi client code is not completely unaware of distribution.
6 Conclusions and Perspectives In this paper we have presented an autonomic approach of a sandbox for the execution of third party components. Third party code such as wrapped native code or untested components may put the application in risk. Our approach allows the execution of such components outside the main application memory space, in a sort of sandbox which provides fault containment without disturbing the trusted part of the application. The sandbox has been validated using two different types of isolation: domain-based and operating system-based. The former has been developed using Java Isolates on a Multi-tasking Virtual Machine, and the latter has been implemented using two Java Virtual Machine instances. A comparison between our implementation using the two approaches shows that the main advantage of domain-based isolation over OS-based isolation concerns the application startup and especially the sandbox reboot time. Memory gains of the domain-based approach are not that significant if we take into account more recent and optimized Virtual Machines. An evaluation of the proposed transparent communication protocol shows that it is 2 to 3 times slower than local RMI, mostly due to the utilization of Java dynamic proxies on both ends. The autonomic manager of the sandbox has been implemented only in the OSbased isolation approach. A control loop for monitoring the sandbox identifies any deviation in system state based on directives defined as scripts which are dynamically loaded by the autonomic manager. The infrasctructure allows to restart individual components or depending on the state and directive, a full reboot on the sandbox. This approach has been tested in a simulation of an RFID and sensor-based application. The correct functioning of the sandbox approach is based on a set of assumptions which may not apply to all types of applications. Another issue that can be pointed out is that the main platform could also be an autonomic element. We plan to transform the sandbox autonomic manager into an OSGi-based application so we could add dynamicity and extensibility to other components. Currently the only flexible part of the autonomic manager is the scripting for rules execution. Of course, the problematic described here of executing third party code would not be the case of this autonomic manager, since no third party code would be involved. Our ultimate goal is to automatically “promote” a component from the sandbox to the trusted platform based on an analysis of a component’s history in the knowledge base. Yet, the promotion is possible but as a manual process done by the application administrator. For now we do not consider that we have enough fine grained information for taking such decision at runtime.
148
K. Gama and D. Donsez
Acknowledgements. The authors thank the anonymous reviewers for their suggestions that helped improving the final version of this paper; Laurent Daynès, from Sun Microsystems, for the advices on the MVM, the discussions about Isolates and domain isolation; and Johann Bourcier, from our research team, for the debates and insights on autonomic computing. Part of this work has been carried out in the scope of the ASPIRE project (http://www.fp7-aspire.eu), co-funded by the European Commission in the scope of the FP7 programme under contract 215417. The authors acknowledge help and contributions from all partners of the project.
References 1. Szyperski, C., Gruntz, D., Murer, S.: Component Software: Beyond Object-Oriented Programming, 2nd edn. Addison-Wesley, Reading (2002) 2. Avizienis, A., Laprie, J.C., Randell, B., Landwehr, C.: Basic Concepts and Taxonomy of Dependable and Secure Computing. IEEE Trans. Dependable Secur. Comput. 1(1), 11–33 (2004) 3. Fox, A., Patterson, D.: Guest Editors’ Introduction: Approaches to Recovery-Oriented Computing. IEEE Internet Computing 9(2), 14–16 (2005) 4. Gray, J.: Why do computers stop and what can be done about it? In: Symposium on Reliability in Distributed Software and Database Systems, pp. 3–12 (1986) 5. Plasil, F., Balek, D., Janecek, R.: SOFA/DCUP: architecture for component trading and dynamic updating. In: 4th Intl. Conf. on Configurable Distributed Systems, pp. 43–51 (1998) 6. OSGi Alliance, http://www.osgi.org/ 7. OSGi Alliance. About the OSGi Service Platform, Technical Whitepaper Revision 4.1, (June 7, 2007), http://www.osgi.org/wiki/uploads/Links/ OSGiTechnicalWhitePaper.pdf 8. Gama, K., Donsez, D.: A Practical Approach for Finding Stale References in a Dynamic Service Platform. In: Chaudron, M.R.V., Szyperski, C., Reussner, R. (eds.) CBSE 2008. LNCS, vol. 5282, pp. 246–261. Springer, Heidelberg (2008) 9. Tian, J.: Software Quality Engineering: Testing, Quality Assurance, and Quantifiable Improvement. Wiley-IEEE Computer Society Press (2005) 10. Candea, G., Kawamoto, S., Fujiki, Y., Friedman, G., Fox, A.: Microreboot — A technique for cheap recovery. In: 6th Conference on Symposium on Operating Systems Design & Implementation (2004) 11. Gama, K., Donsez, D.: Towards Dynamic Component Isolation in a Service Oriented Platform. In: Lewis, G.A., Poernomo, I., Hofmeister, C. (eds.) CBSE 2009. LNCS, vol. 5582, pp. 104–120. Springer, Heidelberg (2009) 12. Kon, F., Campbell, R.H.: Dependence Management in Component-Based Distributed Systems. IEEE Concurrency 8(1), 26–36 (2000) 13. Kephart, J., Chess, D.: The Vision of Autonomic Computing. Computer 36, 41–50 (2003) 14. Ganek, A.G., Korbi, T.A.: The Dawning of the Autonomic Computing Era. IBM Systems Journal 42(1), 5–18 (2003) 15. IBM. An architectural blueprint for autonomic computing. Autonomic computing whitepaper, 4th edn. (2006) 16. Huebscher, M., McCann, J.: A survey of autonomic computing—degrees, models, and applications. ACM Computing Survey 40(3), 1–28 (2008) 17. Candea, G., Kiciman, E., Kawamoto, S., Fox, A.: Autonomous recovery in componentized Internet applications. Cluster Computing 9(2), 175–190 (2006) 18. Huang, Y., Kintala, C.: Software Fault Tolerance in the Application Layer. Software Fault Tolerance. John Wiley, Chichester (1995)
A Self-healing Component Sandbox for Untrustworthy Third Party Code Execution
149
19. Gama, K., Rudametkin, W., Donsez, D.: Using Fail-stop Proxies for Enhancing Services Isolation in the OSGi Service Platform. In: MW4SOC 2008, pp. 7–12. ACM, New York (2008) 20. Czajkowski, G., Daynès, L.: Multitasking without Compromise: a Virtual Machine Evolution. In: 16th conference on Object-Oriented Programming, Systems, Languages, and Applications (OOPSLA), New York, USA, pp. 125–138 (2001) 21. Seinturier, L., Pessemier, N., Escoffier, C., Donsez, D.: Towards a Reference Model for Implementing the Fractal Specifications for Java and the .NET Platform. In: 5th Fractal Workshop at ECOOP 2006 (2006) 22. Moraes, R., Barbosa, R., Duraes, J., Mendes, N., Martins, E., Madeira, H.: Injection of faults at component interfaces and inside the component code: are they equivalent? In: European Dependable Computing Conference, EDCC 2006, pp. 53–64 (2006) 23. Huang, Y., Kintala, C.M.R., Kolettis, N., Fulton, N.D.: Software Rejuvenation: Analysis, Module and Applications. In: 25th International Symposium on Fault Tolerant Computing (1995) 24. Ghosh, D., Sharman, R., Rao, H.R., Upadhyaya, S.: Self-healing systems survey and synthesis. Decision Support Systems 42(4), 2164–2185 (2007) 25. Li, J., Chen, X., Huang, G., Mei, H., Chauvel, F.: Selecting Fault Tolerant Styles for Third-Party Components with Model Checking Support. In: Lewis, G.A., Poernomo, I., Hofmeister, C. (eds.) CBSE 2009. LNCS, vol. 5582, pp. 69–86. Springer, Heidelberg (2009) 26. Bouchenak, S., Boyer, F., Krakowiak, S., Hagimont, D., Mos, A., Jean-Bernard, S., Palma, N.d., Quema, V.: Architecture-Based Autonomous Repair Management: An Application to J2EE Clusters. In: 24th IEEE Symposium on Reliable Distributed Systems, IEEE Computer Society, Washington (2005) 27. Garlan, D., Cheng, S., Huang, A., Schmerl, B., Steenkiste, P.: Rainbow: Architecture Based Self-Adaptation with Reusable Infrastructure. Computer 37(10), 46–54 (1995) 28. Bottaro, A., Bourcier, J., Escoffier, C., Lalanda, P.: Autonomic Context Aware Service Composition. In: IEEE International Conference on Pervasive Services, pp. 223–231 (2007) 29. Diaconescu, A., Maurel, Y., Lalanda, P.: Autonomic Management via Dynamic Combinations of Reusable Strategies. In: 2nd International Conference on Autonomic Computing and Communication Systems (2008) 30. Ferreira, J., Leitao, J., Rodrigues, L.: A-OSGi: A framework to support the construction of autonomic OSGi-based applications. In: Autonomics 2009, Cyprus (2009) 31. Su, R., Chaudron, M.R.V., Lukkien, J.J.: Runtime failure detection and adaptive repair for fault-tolerant component-based applications. In: Software Engineering of Fault Tolerant Software Systems, pp. 230–255. World Scientific Publishing, Singapore (2007) 32. Su, R., Chaudron, M.R.V.: Self-adjusting Component-Based Fault Management. In: 32nd EUROMICRO Conference on Software Engineering and Advanced Applications, pp. 118–125. IEEE Computer Society, Washington (2006) 33. Lowy, J.: COM and.NET Component Services, 1st edn. O’Reilly & Associates, Inc, Sebastopol (2001) 34. Escoffier, C., Donsez, D., Hall, R.S.: Developing an OSGi-like service platform for .NET. In: Consumer Comm. and Networking Conf., USA, pp. 213–217 (2006) 35. Nagel, C., Evjen, B., Glynn, J., Watson, K., Skinner, M.: Professional C# 4 and .NET 4. Wiley Publishing, Chichester (2010) 36. Rellermeyer, J.S., Alonso, G., Roscoe, T.: R-OSGi: Distributed Applications through Software Modularization. In: 8th Intl. ACM/IFIP/USENIX Middleware Conference (2007)
Component Contracts in Eclipse - A Case Study Jens Dietrich and Lucia Stewart School of Engineering and Advanced Technology Massey University New Zealand [email protected], [email protected]
Abstract. We present the results of an experiment investigating component collaborations in the OSGi/Eclipse component model. The aim of the experiment is to demonstrate the benefits of using a formal contract language. For this purpose, we have associated more than 1000 component collaborations in OSGi/Eclipse with formal contracts extracted from component metadata and extension point documentation. We discuss several characteristics of these contracts, including contract complexity, and verification results obtained with these contracts. To our surprise, there are numerous contract violations. This indicates that the use of a formal contract language can significantly improve the quality of dynamically composed systems.
1
Introduction
Component-based software engineering [1] is a widely accepted approach to master the complexity of large software systems by assembling them from simpler, re-usable components. In the past, system assembly was mainly static. At some stage in the software engineering lifecycle, suitable components were selected and a system was assembled by a software engineer. Then this system was verified in order to check whether it was correct and satisfied other quality criteria, and finally it was deployed. In the last decade, new requirements have led to dynamic component models. Systems are no longer assembled and verified by a software engineer, but instead evolve while deployed. That is, components are replaced while systems remain operational. Examples for systems that require dynamic assembly are enterprise systems in businesses operating in different time zones, and systems run by application service providers that guarantee customers a very high level of system uptime through service level agreements (SLAs) but use shared resources for different customers. Examples of dynamic component models include OSGi [2] and its derivatives such as OSGi declarative services [2], iPOJO [3], Eclipse [4] and Spring Dynamic Modules [5]. OSGi is based on Java, is highly standardised and enjoys wide industry support. OSGi components are also called bundles. Bundles have unique ids that use a naming scheme similar to Java name spaces, and they can be distributed using common network protocols like HTTP. In particular, applications can find and install bundles using bundle repositories. The functionality of bundles are described using (Java) interfaces. The interfaces are abstract L. Grunske, R. Reussner, and F. Plasil (Eds.): CBSE 2010, LNCS 6092, pp. 150–165, 2010. c Springer-Verlag Berlin Heidelberg 2010
Component Contracts in Eclipse - A Case Study
151
service descriptions which decouple consumers and providers of services. Bundle metadata contains information about name, version and author and a URL that can be used to obtain newer versions. More importantly, the metadata also contains information about functionality that is exported (to be used by other components) and imported (can be used by this component). This is used by the runtime container to resolve dependencies and to assemble (wire) components. The problem with this approach is that component capabilities can only be described through interfaces. While this fits well into the tradition of objectoriented programming with statically typed programming languages and technologies like interface definition languages (IDLs) [7], it results in component assemblies that are difficult to verify. Verification of dynamic assemblies is challenging - it has to be performed automatically by the runtime container whenever the configuration changes. Given the expressiveness of existing metadata, the container can only check interface interoperability and version compatibility. This leaves a lot of room for error. In particular, this makes it almost impossible to verify correct system behaviour (semantics) and quality of service attributes. The Eclipse component model is based on OSGi but adds some interesting features to it. While Eclipse had originally been designed as a development environment, it has evolved over time into a full-fledged, general-purpose application platform. The purpose of the Eclipse component model is to support an “architecture of participation” [8]. This has obviously been successful, and has led to a large ecosystem of Eclipse components (also called plugins). In Eclipse, services provided and consumed by plugins can not only be services described by (Java) interfaces but also other resources such as XML or property files. The Eclipse environment does not restrict the kind of resource that can be provided by a plugin. This is facilitated by the Java class loader application programming interface (API) used by plugins: class loaders do not only provide classes but also resources represented as URLs and their associated input streams. Plugins use extension points to define what kind of resource they can consume, and extensions to define the resources they can provide. Details are either defined in the plugin.xml metadata file that is used to define extensions and extension points, in the extension point schema or in the documentation associated with extension points. A common example for the use of resources other than Java types is that plugins have to provide an XML resource that has a structure defined by a document type definition (DTD). This approach is used in the .help.toc1 extension point. It specifies the structure of a table of content file that is used to organise help pages. This is used by plugins to add help pages to the central help system. One would expect that this DTD is used for automated system verification to safeguard composition. However, this is not the case. The DTD is merely a documentation to guide the developer, and the structure of the table of
1
Extensions and extension points in Eclipse are named using Java package like identifiers. From now on, we skip the common org.eclipse. prefix and replace it with a single . to save space.
152
J. Dietrich and L. Stewart
content document is checked implicitly when the table of content file is parsed in the class org.eclipse.help.search.HelpIndexBuilder. The fact that services are generalised to resources makes it attractive to use semantic web technology to describe those resources and their relationships. The basic idea is to consider the resources provided by plugins offering functionality such as classes and XML documents, the resources of consuming plugins such as interfaces and DTDs, and the relationships between them as contractual specifications. In our previous work [9], we have proposed Treaty, a formal contract language based on these principles. However, specifying contracts itself requires effort and the question arises whether this effort is justified by the benefits. The main contribution of this paper is to address this question. We do this using an experiment where we extract and formalise contracts from an existing complex software system, and then verify this very system against those contracts. Our assumption is that using formal contracts would be useful if verification failed for a significant number of contracts. It turns out that this is the case. The setup of the experiment requires an extension of Treaty to facilitate the a posteriori association of contracts with components. This is also a conceptually interesting feature as it allows to define contextual contracts. This extension, called contract injection, is the second contribution of this paper. This paper is organised as follows. In chapter 2, we briefly introduce Treaty. In chapter 3 we discuss contract injection. This is then used in an experiment described in chapter 4. Finally, we conclude our contribution in chapter 5. This includes a discussion of related and future work.
2 2.1
Component Contracts Contract Types
When composing software from parts, rules must be followed to ensure that the result of the composition is correct. A typical example is the use of types in many strongly typed mainstream programming languages, and the use of compilers to enforce these rules. While this is useful, many rules cannot be expressed when relying on the programming language type system only. In particular, the type system can only be used in order to check the correctness of types, but not instances and values. There are many approaches targeting the problem of semantic specification. Two of them had a major impact, Bertrand Meyer’s design by contract[10] - an approach to add constraints directly to the programming language to describe the state changing effects of method invocations, and language embedded unit testing by Kent Beck and Erich Gamma[11], a popular approach where the respective constraints are defined for specimen instances of the types to be tested. It has been pointed out [12] that there are other types of contracts as well, in particular quality of service contracts that refer to attributes like computing speed and memory consumption. This is increasingly important for many applications that use shared or restricted resources. Another type of contracts refers to licensing. For instance, many organisations restrict the use
Component Contracts in Eclipse - A Case Study
153
of components licensed under propagating licenses such as the general public license (GPL). Formalising these contracts would allow the automated verification of assemblies against these requirements. What is therefore desirable is a framework that supports the easy representation of all these types of contracts. This language should also support contracts that refer to artefacts with different metamodels, such as XML documents, property files, Velocity templates, programming language artefacts such as Java classes and interfaces, etc. We use the term metamodel here as follows: a metamodel defines the syntax and the semantics of resource types and their properties and relationships. In particular, a metamodel is more than the definition of resource types, properties and relationships in a formal ontology language such as OWL [13]. A metamodel also specifies rules that allow to check whether a resource is of a certain type, whether resources have a certain property or participate in a relationship. Meta models often achieve this through the provisioning of tools. Examples are the Java compiler, the Java virtual machine (JVM), the JUnit test runner, the Velocity parsers or XML validators. In [9] we have proposed such a contract language called Treaty. Treaty is based on the idea that collaborating components interact through typed resources. On the one hand, components use resources such as Java classes and XML documents supplied by suppliers. However, the consumers also supply resources that are used to express constraints on the resources that they can consume. Examples for this are Java interfaces or test suites constraining Java classes, and document types definitions or XML schemas to constraint XML documents. 2.2
Resources
As discussed above, components can be portrayed as suppliers and consumers of resources. These resources have to be referenced in contracts. Not all resources can be directly addressed. To support dynamic component models, two kinds of resources must be considered: static and dynamic. Typically, the consumer component has static resources representing requirements. This means that at the point in time when the contract is written the consumer and its requirements are known, and consumer resources can be identified by (relative) resource locators (URLs). What is dynamic however are the providers of resource matching these requirements. In general, the respective components will advertise their services through their metadata. The structure of these metadata depends on the particular component model. Treaty supports this by providing a component model independent framework abstraction layer, and an implementation layer for particular component models such as OSGi/Eclipse. The details how resources can be extracted from component metadata are specified in the implementation layer for the respective component model. For instance, OSGi components can list properties in a plain text file (the bundle manifest) as key-value pairs, whereas in Eclipse an XML file (plugin.xml) can also be used for this purpose. The actual semantics of the functions represented by resource references is component-model specific. In Eclipse, the plugins providing resources define the resource names (as URLs relative to the providing component) in the plugin.xml
154
J. Dietrich and L. Stewart
metadata file. For instance, the resource reference used in the example below (listing 1.1, line 12) is an XPath expression. That is, for a given provider component, the function is resolved by executing the respective XPath query against the component metadata, and obtaining the resource name from the query result. The process of resolving references with respect to a supplier component is called binding, the result of binding all resources in a contract instantiates it and produces a contract instance. 1 2 3 <e x t e r n a l> 4 5 h t t p : //www. t r e a t y . o r g /xml#DTD typ e> 6 c o n t r a c t r e s o u r c e s / o r g . e c l i p s e . h e l p . t o c / t o c . dtd 7 r e s o u r c e> 8 e x t e r n a l> 9 <s u p p l i e r> 10 11 h t t p : //www. t r e a t y . o r g /xml#XMLInstance typ e> 12 < r e f>/ t o c / @ f i l e r e f> 13 r e s o u r c e> 14 s u p p l i e r> 15 16 17 <not><must E x i s t r e s o u r c e=” t o c ” /> not> 18 < r e l a t i o n s h i p r e s o u r c e 1=” t o c ” r e s o u r c e 2=” t o c d t d ” 19 ty pe=” h t t p : //www. t r e a t y . o r g /xml#i n s t a n t i a t e s D T D ” /> 20 o r> 21 c o n s t r a i n t s> 22 c o n t r a c t>
Listing 1.1. An external contract
2.3
Constraints
The actual contract conditions are expressed using constraints. There are two kinds of constraints: basic and complex. Basic contract constraints can be of three types: 1. a resource of a certain type must exist 2. a resource must have a certain property 3. a resource must have a certain relationship with another resource Property and relationship constraints are similar to data and object properties in RDF. Existence constraints only state that the respective resource must exist and must be of the correct type. Complex constraints can be defined recursively using the connectives from classical propositional logic. Supported are NOT, OR, AND and XOR with their usual semantics.
Component Contracts in Eclipse - A Case Study
2.4
155
Vocabularies
In Treaty, resource, relationship and property types form a vocabulary. The vocabulary can be defined in a formal ontology. In the Eclipse-based Treaty implementation, the vocabulary is assembled dynamically from plugins providing extensions for a special vocabulary extension point, so-called vocabulary contributions. Each plugin extending this extension point provides the following: 1. 2. 3. 4.
a list of types contributed by this vocabulary (list of URIs) a list of relationships contributed (list of URIs) a list of properties contributed (list of URIs) the relationships of defined types, relationships and properties to other types, relationships and properties 5. a service to load and check the existence of resources of the contributed types 6. a service to check whether two resource instantiate a contributed relationship 7. a service to check whether a resource fulfils a condition for one of the contributed properties Vocabulary contributions have therefore two roles: they provide syntax elements (1-4) and the semantics for these elements (5-6). Vocabulary contributions are adapters for the different metamodels defined earlier. The syntax elements can be aggregated to a virtual (OWL) ontology that can then be used by the vocabulary contributions for reasoning. Details of this technique are beyond the scope of this paper, the reader is referred to [9] for use cases and design details.
3
Contract Injection and Aggregation
Contracts can be considered as part of the metadata of the consuming component: they are used by the component to specify its requirements. In the Treaty implementation for Eclipse, this point of view is reflected by the technique used to load contracts: contracts are located through naming patterns. More precisely, contracts can be associated with an extension point by naming them using the extension point name followed by the .contract extension, and placing them in the META-INF folder of the plugin that owns the extension point. However, there are use cases that require a different approach. In particular, the kind of contract needed might be context-specific. For instance, consider a component used in systems that are deployed in the following scenarios: 1. 2. 3. 4.
a university student lab a mobile platform with limited hardware capabilities a hospital, supporting life-saving equipment a server run by an application service provider with service level agreement (SLA) contracts guaranteeing a high degree of reliability to clients
156
J. Dietrich and L. Stewart
While one could argue that there should always be a core contract that includes constraints with respect to interface and behavioural interoperability, there should be additional constraints that are context-dependent. For instance, scenario 3 might require additional constraints safeguarding functionality such as test cases, while scenarios 2 and 4 would require additional quality of service constraints. In order to support these use cases, we propose to use external contracts. External contracts are defined outside the consumer component. In particular, they can refer to resources not defined by the consumer plugin, such as additional test cases that are then used in additional conditions. For instance, consider the contract shown in listing 1.1. This is (a short version of) a contract for Eclipse that describes the requirements of the help system table of content extension point (.help.toc). In Eclipse, these requirements are documented in the respective extension point reference page. This page contains the embedded document type definition defining the format of the table of content files. Using external contracts, the DTD can be copied into a standalone document and a contract can be defined that references this DTD (lines 4-7 in the listing). This document is a resource that is not attached to the consumer component, but part of a third component that is also used to define the contract. This way, contracts can be associated with components without modifying them. We call this contract injection. Note that the contract uses an <external> element (line 3) instead of the element used in contracts directly attached to consumers. An obvious problem is that the verification framework needs to associate the contracts with the correct components. In general, this problem can not be solved on the framework level. For the Eclipse-based implementation, we have addressed this problem by introducing an extension point for contracts. This allows the use of the base component model to define and organise contracts. In particular, the contract framework itself can be used to safeguard the correctness of contract definitions: the Treaty plugin that defines the contract extension point also owns the contract (XML) schema as a resource (treaty.xsd), and the contract for contracts requires that the contracts are valid with respect to this schema (listing 1.2). A similar approach has been used for the vocabulary extension point: extending plugins are checked whether they provide an OWL resource to define types, properties and relationships, and a class implementing a fixed interface used to define the semantics of the properties and relationships. At runtime, built-in and additional external contracts can be easily merged into one instantiated contract by using an additional conjunction at the top level of the constraint hierarchy. We call this an aggregated contract. While the ability to define contracts outside collaborating components addresses the use cases discussed above, it also has another benefit. It can be used to associate contracts with existing sealed components in a non-invasive manner. Sealed means that the components are either explicitly protected (for instance, by digital signatures), or changing them is too expensive as it would create a separate maintenance branch and disconnect the component from its repository. Therefore, this technology is suitable to add contracts to existing “legacy” systems.
Component Contracts in Eclipse - A Case Study
157
1 2 3 4 h t t p : //www. t r e a t y . o r g /xml#XMLSchema typ e> 5 t r e a t y . xsd 6 r e s o u r c e> 7 consumer> 8 <s u p p l i e r> 9 10 h t t p : //www. t r e a t y . o r g /xml#XMLInstance typ e> 11 < r e f>c o n t r a c t / @ l o c a t i o n r e f> 12 r e s o u r c e> 13 s u p p l i e r> 14 15 < r e l a t i o n s h i p r e s o u r c e 1=” C o n t r a c t ” r e s o u r c e 2=” ContractSchema ” 16 typ e=” h t t p : //www. t r e a t y . o r g /xml#i n s t a n t i a t e s ” /> 17 c o n s t r a i n t s> 18 c o n t r a c t>
Listing 1.2. The Eclipse contract for contracts
A potential risk of contract injection is the possibility of denial of service attacks. For instance, if a system was set up to automatically perform actions such as uninstalling components when verification of these component fails, then the injection of malicious contracts failing for all components could stop the entire system. However, the same mechanisms used to protect other components such as the use of digital signature can be used to protect systems against this kind of attacks.
4
Verifying Eclipse
4.1
Experiment Setup
Using contract injection, existing Eclipse plugins can be retrofitted with contracts. We have selected a set of 25 extension points for this purpose from the Eclipse Ganymede EE2 distribution. The selection was done based on the number of extensions available. In the distribution used, there are 1139 extensions defined for these extension points. That is, we have investigated 1139 component collaborations. The starting point for extracting contracts are the extension point documentation pages. They describe contracts in a semi-formal fashion. For extension points that consume XML documents, the DTD defining the structure of the XML is often embedded in the extension point documentation and can easily be extracted as separate resource that is then placed in the plugin defining the contracts. Examples for this are the .help.toc and .help.contexts extension points. 2
Eclipse IDE for Java EE Developers, SR1, using the Java Runtime Environment version 1.6.0 07.
158
J. Dietrich and L. Stewart
Many extension points define interfaces and consume classes. In this case we have made the contract plugin dependent on the plugin owning the respective extension point so that the plugin is able to load the respective interface(s). Examples where this is used are .ui.actionSets and .ui.commands. We have found a lot of contracts that required that resources had to satisfy certain conditions, but the existence of the resource was not explicitly required. In this case, we have used the following pattern to formalise contracts: NOT (exists R) OR condition(R). This is, we have used material implication to represent this (IF (exists R) THEN condition(R)). Finally, we have inspected the documentation of public interfaces referenced in the extension point definitions. Here we were looking for further constraints beyond types. We have found only one, in the extension point .ui.handlers. This is discussed in more detail below. We do not claim that we have extracted the complete contracts using this method. It is very likely that there are more contract elements, in particular hidden in the source code of classes that consume resources. However, these contracts would not be part of the public interface of the component, and software engineers following the “invitation rule” [8] would usually not try to locate and investigate the source code of (internal) classes to understand component interfaces. However, the programmers who have written these classes would be aware of hidden contracts and write components taking advantage of this knowledge. This would result in a clear violation of another of the Eclipse “house rules”, the “fair play rule” [8]. To implement the experiment, we have created an Eclipse plugin net.java.treaty.eclipse.system3. This plugin has the contracts as well as the resources referenced by the contracts. In particular, the plugins has several DTDs. The Java interfaces referenced in contracts are not explicitly defined in this plugin but integrated by using dependencies to the plugins defining them. This approach is consistent with the hierarchical classloader architecture used in Eclipse. 4.2
Contract Complexity
In order to assess complexity and character of the contracts, we use the following two metrics. Firstly, the contract depth (DE) is recursively defined as follows: 1. If C is an aggregated contract and {Ci } are its parts, then DE(C) := 1 + M AX({DE(Ci )}) 2. If C is a simple (non aggregated) contract that has multiple constraints {constri }, then DE(C) := 1 + M AX({DE(constri )}) 3. If C is a simple contract that has only one constraints constr, then DE(C) := DE(constr) 3
This plugin is stored in the public project repository, the URL is http://treaty.googlecode.com/svn/trunk/treaty-eclipse-example/treaty-eclipseexample-system/
Component Contracts in Eclipse - A Case Study
159
Table 1. Contract metrics and used resource types by vocabulary metrics XML Java DE RE T R XML DTD XSD Inst. Abstr. extension point Type net.java.treaty.eclipse.contract 0 1 2 1 1 0 1 0 0 net.java.treaty.eclipse.vocabulary 1 1 3 1 0 0 0 1 1 .ant.core.extraClasspathEntries 0 0 0 0 0 0 0 0 0 .core.expressions.definitions 3 0 0 0 0 0 0 0 0 .core.expressions.propertyTesters 1 1 2 1 0 0 0 1 1 .core.resources.markers 3 0 0 0 0 0 0 0 0 .core.runtime.adapters 1 1 2 1 0 0 0 1 1 .core.runtime.applications 3 1 2 1 0 0 0 1 1 .core.runtime.contentTypes 3 2 2 1 0 0 0 2 1 .core.runtime.preferences 1 0 1 0 0 0 0 3 0 .help.contexts 1 2 4 2 1 1 0 1 1 .help.toc 3 2 4 2 1 1 0 1 1 .team.core.fileTypes 0 0 0 0 0 0 0 0 0 .ui.actionSetPartAssociations 0 0 0 0 0 0 0 0 0 .ui.actionSets 4 2 2 1 0 0 0 1 2 .ui.bindings 2 0 0 0 0 0 0 0 0 .ui.commands 3 5 2 1 0 0 0 5 3 .ui.contexts 1 0 0 0 0 0 0 0 0 .ui.editorActions 3 1 2 1 0 0 0 1 1 .ui.editors 3 4 2 1 0 0 0 4 4 .ui.handlers 3 4 3 2 0 0 0 2 1 .ui.keywords 1 0 0 0 0 0 0 0 0 .ui.newWizards 1 1 2 1 0 0 0 1 1 .ui.perspectiveExtensions 1 0 0 0 0 0 0 0 0 .ui.preferencePages 0 1 2 1 0 0 0 1 1 .ui.propertyPages 3 1 2 1 0 0 0 1 1 .ui.views 4 1 2 1 0 0 0 1 1
JUnit Testcase 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0
OWL OWL 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
4. If constr = constr1 ⊕ constr2 ⊕ .. ⊕ constrn , where ⊕ is one of the logical connectives AND, OR, XOR or NOT (for NOT, n must be 1), then DE(constr) := 1 + M AX({DE(constri )}) 5. If constr is an existence, property or relationship constraints, DE(constr) := 0 This is a straight forward recursive definition that measures the depth of the tree formed by the instantiated contract. Condition 2 states that contracts with multiple conditions use an implicit AND node, and their complexity is at least 1. The second metric is the number of relationships in the contract (RE). This is the number of significant leaf notes in the tree. Both metrics measure contract complexity. Finally, we measure contract diversity by counting the number of referenced resource types (T) and the number of relationship types (R). For the contract listed in listing 1.1, the values for the respective metrics are as follows. The contract is simple (non aggregated), and has a negated mustExist condition inside a disjunction. Therefore, the contract depth (DE) is 2. The contract has only one relationship (in line 18), therefore RE and R are both
160
J. Dietrich and L. Stewart
1. Finally, the cntract uses the two different resource types DTD (line 5) and XMLInstance (line 11). Therefore, T is 2. Table 2 summarises the results obtained for those metrics, and also shows the number of resources referenced by resource type. The table contains contracts for the 25 extension points analysed plus the two additional Treaty extension points for the contract and vocabulary contribution extension points. The table lists the contracts identified by the unique extension point id, and shows the values for the respective metrics and the number of referenced types grouped by vocabulary. Not all contracts reference resources defined in one of the vocabularies listed. These are cases where the plugin must only define string values. Often, these contracts have additional properties that state that the value must be one from a given list, or match a certain regular expression. Some contracts are quiet complex. An example for this .ui.actionSets. Here, the value of the attribute class must be a name of a class that implements a certain Java interface. Which interface this is depends on the value of another attribute (style). Therefore, disjunction must be used in this contract. Disjunction indicates that providers of functionality can use different strategies to fulfil requirements. Table 2. Verification results summary extension point
plugin (owner)
.ant.core.extraClasspathEntries .core.expressions.definitions .core.expressions.propertyTesters .core.resources.markers .core.runtime.adapters .core.runtime.applications .core.runtime.contentTypes .core.runtime.preferences .help.contexts .help.toc .team.core.fileTypes .ui.actionSetPartAssociations .ui.actionSets .ui.bindings .ui.commands .ui.contexts .ui.editorActions .ui.editors .ui.handlers .ui.keywords .ui.newWizards .ui.perspectiveExtensions .ui.preferencePages .ui.propertyPages .ui.views
ant.core .core.expressions .core.expressions .core.resources .equinox.registry .equinox.app .core.runtime .core.runtime .help .help .team.core .ui .ui .ui .ui .ui .ui .ui .ui .ui .ui .ui .ui .ui .ui sum:
number of extending plugins 13 6 39 44 82 43 38 76 61 46 28 31 56 39 73 25 20 38 26 37 51 39 105 65 58 1139
basic instances 15 21 65 98 236 43 85 76 61 46 28 50 398 39 73 25 52 64 91 37 113 83 282 170 103 2354
contracts failed 0 0 0 0 4 7 0 0 32 5 0 0 1 0 0 0 0 0 3 0 1 1 2 0 1 56
Component Contracts in Eclipse - A Case Study
161
Some contracts define resources but have no relationship constraints. An example is .core.runtime.preferences. These contracts use only property and existence constraints. There are several contracts which use resources from different metamodels. In particular, a mix of XML and Java resources is used in the two .help.* contracts. The table also contains the two system contracts from the Treaty framework itself. The vocabulary contract uses an OWL resource to describe the types and relationships contributed to the contract vocabulary. Finally, we have found one example of an Eclipse contract that specified a semantic condition that could be translated into a test constraint. The .ui.handlers extension point references the interface org.eclipse.core.commands. IHandler. This interface defines the public method Object execute(ExecutionEvent event) throws ExecutionException, and the public documentation4 of this method states that the event parameter represents “an event containing all the information about the current state of the application; must not be null”. Furthermore it states that the the exception is to be thrown “if an exception occurred during execution”. This indicates that an execution exception is thrown if the parameter is null. This constraint can be expressed by a simple JUnit4 test case, as shown in listing 1.3. The test case uses constructor dependency injection. This is not directly supported by JUnit. For this reason, the Treaty framework contains an extension of JUnit that supports dependency injection. 1 public c l a s s I H a n d l e r T e s t s { 2 private I H a n d l e r h a n d l e r = null ; 3 // c o n s t r u c t o r t o s u p p o r t dependency i n j e c t i o n 4 public I H a n d l e r T e s t s ( I H a n d l e r h a n d l e r ) { 5 super ( ) ; 6 this . handler = handler ; 7 } 8 @Test ( e x p e c t e d=E x e c u t i o n E x c e p t i o n . c l a s s ) 9 public void t e s t 1 ( ) { 10 h a n d l e r . e x e c u t e ( null ) ; 11 } 12 } 13 }
Listing 1.3. JUnit test case for a not null constraint
4.3
Verification Results
The main purpose of contracts is verification. That is, the instantiated contracts can be checked whether the contract constraints are satisfied. For complex constraints, this is done recursively according to the semantics of the boolean connectives used. For basic constraints, verification is done by the components 4
http://help.eclipse.org/ganymede/index.jsp
162
J. Dietrich and L. Stewart
Table 3. Contract violations in the contracts for .help.toc and .help.contexts
extension point
illegal syntax element
.help.toc .help.toc .help.contexts .help.contexts .help.contexts .help.contexts .help.contexts .help.contexts
element enablement attribute extradir element b element p element code element description element topic attribute filter
number of violations 3 2 27 1 1 1 1 1
example plugin .platform.doc.user .jdt.doc.user .platform.doc.user .wst.xml.ui.infopop .datatools.oda.cshelp .jdt.doc.user .rse.ui .birt.cshelp
that contribute the respective vocabulary. For instance, these components have Java classes with methods that can check whether two resources r1 and r2 instantiate a certain relationship rel, that is, whether (r1 , r2 ) ∈ rel. This task is usually delegated to a tool that is part of the respective metamodel, such as the Java Virtual Machine, a JUnit test runner or a validating XML parser. Table 3 summarises the verification results. The full results are available online5 . It is surprising to see that some contracts fail. This indicates that the contracts are not actually fully enforced by the plugin that consumes the resource provided by the extension. We analyse some of the results in more details. The group with the highest number of contracts failing are the two extension points .help.contexts and .help.toc. For .help.contexts, more than 50% (32/61) of all contract instances fail for those contracts. These two contracts use a constraint that checks whether a provided XML resource is valid with respect to a document type definition. The DTD embedded in the documentation is not complete in the sense that it does not describe embedded markup that is used by many extensions. Details of the contract violations are shown in table 4. This table shows the ids of the extension points defining the contracts, the illegal syntax elements found in the XML documents, the number of violations found, and the name of one plugin where this violation occurs. It appears that many of the violations are related to the use of HTML markup in the table of content document. However, it is not documented in the format definition that this is actually allowed. The contract for the extension point .core.runtime.applications fails for 7 extensions. The contract requires that instantiating plugins provide a class implementing the interface org.eclipse.equinox.app.IApplication. However, these 7 plugins don’t do this. In particular, some use the older interface org.eclipse.core.runtime.IPlatformRunnable instead. Interestingly, this interface is marked as deprecated. An example of a plugin where this is done is .birt.report.engine. It is easy to understand why Eclipse does not enforce the contract here - this is to ensure compatibility with older versions, and to 5
http://www-ist.massey.ac.nz/jbdietrich/treaty/treatyout/index.html
Component Contracts in Eclipse - A Case Study
163
hope that developers will use compiler warnings to upgrade to the newer API. The four contract instances that fail for the .core.runtime.adapters contract all have a common problem. According to the extension point documentation, the factory element in plugin.xml has a required attribute class that must be the fully qualified name of a class. For those four contract instances, an empty string is used. An example for this is the plugin .debug.ui. As discussed above, the contract for .ui.handlers contains the behavioural (semantic) condition that can be expressed as a test case. This test case fails for one contract instance, .pde.runtime. This means that it is possible to invoke the execute method with a null parameter without the specified exception being thrown.
5
Conclusion, Related and Future Work
In this paper, we have presented a case study showing how the Treaty contract framework can be applied to Eclipse. We did this by formalising some of the public component contracts found in Eclipse. We then obtained data by measuring metrics for these contracts, and by verifying a large Eclipse distribution against these contracts. We think that the outcomes of this study support the claim that it is useful to employ a formal contract language. Using a formal language makes contracts unambiguous. An argument that is often brought forward against formal techniques is their complexity. We believe that Treaty is a simple language based on the simplicity of RDF - constraints are expressed in a SPO (subject-predicateobject) structure. The outcomes of the study also show that the expressiveness of Treaty is appropriate. In particular, disjunction is used in real world contracts. The benefits of the contract language have to balanced against the costs of creating and maintaining the contracts. We believe that the contract language is simple enough to be accessible to average software engineers. However, this could be further improved by providing tooling such as a domain specific contract language (not based on XML), user interface components to edit and visualise contracts, and integration with tools like refactoring browsers. The complexity of many contracts could be easily reduced by adding native support for material implication to the language. The fact that a significant number of contracts fail seems to indicate that the Eclipse distribution analysed suffers from quality problems. This is not necessarily the case. We only harvested contracts from public documentation, in particular extension point descriptions and generated interface documentation. There are of course more contracts present, hard-coded in the classes that consume the resources provided by plugins. While one could argue that the logic embedded in source code is also part of the documentation as the software is open source, it is not realistic to expect from developers to understand the full complexity of framework classes to extend them. However, insider knowledge of these classes gives developers an (unfair) advantage, violating one of the core social rules of the Eclipse eco-system, the “fair play rule”.
164
J. Dietrich and L. Stewart
The main advantage of having formal contracts that are part of the public interface is of course that they can be processed by tools. The verification tool we have developed as part of Treaty and used to extract the data presented is an example. We believe that automated verification will become more and more important as component models support evolving systems. Verification can safeguard evolution by checking the consistency of systems after lifecycle events such as system upgrades. During this study, we have found one good reason not to put constraints into public contracts. This is related to the management of legacy code. It is sometimes not practical to enforce newer version of the contract that would break a large number of existing components. A possible solution would be two different contract layers, one that produces verification errors and one that produces verification warnings. This would be similar to the approach taken by the Java compiler and how @deprecated annotations are handled. To the best of our knowledge our approach is unique in that it addresses the problem of different contract types in dynamic, heterogeneous component models. In [14], the authors propose the use of Prolog to express contracts between design components. In contrast to our work, the authors do not consider dynamic and heterogeneous component models. Some authors have explored the possibility of manual and automated contract extraction from Java [15,16] and .NET programs [17], and the formal representation of these contracts. Arnout and Meyer went on and tried to show the benefits of an a posteriori addition of contracts [18]. They set out to resolve the “closet contract conjecture” [18]. This conjecture states that there are implicit contracts in software “lurking under the cover”, and that making these contract explicit can significantly improve the quality of systems. The intention of this study is very similar to ours. However, the contract language and the nature of the component models analysed are very different. Arnout and Meyer distinguish between “a posteriori contracting” vs. “contracting from the start”. Treaty goes one step further and supports the combination and aggregations of internal and external contracts. We believe that the Treaty framework is now mature enough to be integrated into real world component models. Current work focuses on improved tool support, including a visual contract language. An interesting issue for further investigation is the interaction of contracts with the component lifecycle. In particular, contract checking forces the initialisation of the respective components. In frameworks like Eclipse that make heavy use of the lazy initialisation design pattern, this is not desirable. As far as the ontology design is concerned, we have found some interesting issues with ontology modularisation. One problem is that vocabulary contributions can use an OWL API in order to reason about the vocabulary when verification is performed. While this is very useful in order to optimise verification, it imposes dependency relationships between these vocabulary contributions. This may require additional constraints on how these vocabulary contributions are organised so that dependency cycles can be prevented.
Component Contracts in Eclipse - A Case Study
165
Acknowledgements This work was supported by funding from the New Zealand Foundation for Research, Science and Technology (FRST) for the Software Product and Project Improvement (SPPI) project.
References 1. Szyperski, C.: Component Software: Beyond Object-Oriented Programming. ACM Press and Addison-Wesley, New York (1998) 2. The OSGi Alliance (2010), http://www.osgi.org 3. iPOJO - A flexible and extensible service component model (2010), http://felix.apache.org/site/apache-felix-ipojo.html 4. The Eclipse Project (2010), http://www.eclipse.org 5. Spring Dynamic Modules for OSGi(tm) Service Platforms (2010), http://www.springsource.org/osgi 6. Nestor, J., Newcomer, J., Gianinni, P., Stone, D.: IDL, The Language and its Implementation. Prentice Hall Software Series, Englewood Cliffs (1990) 7. Lamb, D.: IDL: sharing intermediate representations. ACM Trans. Program. Lang. Syst. 9(3), 297–318 (1987) 8. Gamma, E., Beck, K.: Contributing to Eclipse: Principles, Patterns, and Plugins. Addison Wesley Longman Publishing Co., Inc, Redwood City (2003) 9. Dietrich, J., Jenson, G.: Components, contracts and vocabularies - making dynamic component assemblies more predictable. Journal of Object Technology 8(7), 131– 148 (2009), http://www.jot.fm/issues/issue_2009_11/article4/index.html 10. Meyer, B.: Applying ”Design by Contract”. Computer 25(10), 40–51 (1992) 11. JUnit.org Resources for Test Driven Development (2010), http://www.junit.org 12. Beugnard, A., J´ez´equel, J.-M., Plouzeau, N., Watkins, D.: Making components contract aware. Computer 32(7), 38–45 (1999) 13. McGuinness, D.L., van Harmelen, F.: OWL Web Ontology Language Overview. Technical report (February 2004), http://www.w3.org/TR/2004/REC-owl-features-20040210/ 14. Dong, J., Alencar, P., Cowan, D.: On analysis of design component contracts: A case study. International Workshop on Software Technology and Engineering Practice 0, 103–113 (2003) 15. Henkel, J., Diwan, A.: Discovering algebraic specifications from java classes. In: Cardelli, L. (ed.) ECOOP 2003. LNCS, vol. 2743, Springer, Heidelberg (2003) 16. Milanovic, N., Malek, M.: Extracting Functional and Nonfunctional Contracts from Java Classes and Enterprise Java Beans. In: Proceedings of the Workshop on Architecting Dependable Systems (WADS 2004) at the International Conference on Dependable Systems and Networks, DSN 2004 (2004) 17. Arnout, K., Meyer, B.: Uncovering Hidden Contracts: The.NET Example. Computer 36(11), 48–55 (2003) 18. Arnout, K., Meyer, B.: Finding Implicit Contracts in.NET Components. In: de Boer, F.S., Bonsangue, M.M., Graf, S., de Roever, W.-P. (eds.) FMCO 2002. LNCS, vol. 2852, pp. 285–318. Springer, Heidelberg (2003)
Automated Creation and Assessment of Component Adapters with Test Cases Oliver Hummel and Colin Atkinson Software Engineering Group, University of Mannheim 68131 Mannheim, Germany {hummel,atkinson}@informatik.uni-mannheim.de
Abstract. The composition of new applications from pre-existing parts has been one of the central notions in software reuse and component-based development for many years. Recent advances with component retrieval technologies and dynamically reconfiguring systems have brought the automated integration of components into systems into the focus of research. Even when a component offers all functionality needed by the using environment there is often a host of “syntactic obstacles” and to date there is no general solution available that can automatically address syntactic mismatches between components and their clients. In this paper we present an approach that automatically creates all syntactically feasible adapters for a given component-client constellation and selects the semantically correct one with the help of “ordinary” unit test cases. After explaining how our approach works algorithmically, we demonstrate that our prototype implementation is already able to solve a large fraction of the adaptation challenges previously identified in the literature fully automatically.
1 Introduction As Brook’s “No Silver Bullet” article [1] famously highlighted, software development deals with complex problems and is thus an inherently complex undertaking. Splitting software systems into more manageable subparts following the “divide and conquer” principle has hence been part of the toolset of software engineers for a long time [2]. Inspired by other engineering disciplines, McIlroy proposed the reuse of existing software artifacts as a means to reduce the effort involved in software development over four decades ago [3]. A number of years later, the ideas in his seminal paper not only gave rise to the notion of component-based development [4], they arguably also paved the way for the recent emergence of (web) services as a key technology of enterprise computing [5]. Similar to object-oriented software development, both approaches are based on the underlying metaphor of a jigsaw puzzle: the assembly of a “whole” based on smaller parts. And just like the pieces in a jigsaw puzzle, a number of objects, components or services needs to be placed together with their appropriate neighbors to yield the desired “whole”. This process is known as composition in the component community and as orchestration in the web service community. Generally speaking, in a software system, the individual connections between the pieces (i.e. between a client and a server component) are established through so-called interfaces comprising syntactic descriptions of operation signatures (comparable to L. Grunske, R. Reussner, and F. Plasil (Eds.): CBSE 2010, LNCS 6092, pp. 166–181, 2010. © Springer-Verlag Berlin Heidelberg 2010
Automated Creation and Assessment of Component Adapters with Test Cases
167
the outer form of the puzzle pieces) and semantic descriptions of the functionality (comparable to the picture on the puzzle pieces if you will). Clearly, both descriptions can cause a mismatch when a part needs to be integrated into an existing system. A semantic mismatch occurs when a piece has the wrong picture (i.e. incorrect functionality) and can thus typically only be detected after the pieces have been put together and some test cases have been executed. Furthermore, it does not make much sense to attempt to perform larger modifications to the functionality of a part since it is more likely simply the wrong building block for the purpose in hand than a malformed part. A syntactic mismatch, however, occurs when a piece has the right picture but the incorrect shape (i.e. incorrect operation signatures) and thus can be easily detected by the compiler or the runtime environment. Obviously, developers that need to deal with syntactic mismatches may alter the interface of either the server or the client component to fix the incompatibilities. However, this makes most sense early in the development process [6] and becomes much more expensive and difficult if, for example, a part needs to be replaced at run-time in a dynamically reconfigurable system. Consequently, in this context, the non-invasive insertion of an adapter class in between two components treated as unmodifiable black boxes yields many advantages. It works analogously to the way in which power adapters are used to connect poweroutlets with plugs from foreign countries. Of course, transferring this approach to software components is not a new idea: it has been around for such a long time that it is listed in practically every catalogue of development patterns such as for example the one by the Gang of Four [7]. As the comprehensive survey of Kell [8] underlines, adaptation is important for, and thus has been influenced by, a number of different software engineering research communities. Amongst developers, however, adaptation is often perceived as a tedious and error-prone activity that requires extensive testing, especially when interfaces are not well documented. Clearly, this perception and its impact make software adaptation a valuable target for automation. The central theme of our article is thus to present a fully automated solution for overcoming syntactic mismatches that often arise when components are to be used and deployed in a new environment, whether this be in the context of reuse [3] or in the context of dynamically reconfigurable systems [22]. As we will discuss in more detail in section 5 we have found that it is feasible to use ordinary unit test cases to drive the automatic creation of adapters for software components. Before presenting this, we continue our article in section 2 by explaining important foundations of component-based software development and reuse. We especially elaborate on the test-driven reuse approach that initially trigged the development of our solution for automated adaptation. In section 3, following that, we explain how mismatching components are typically adapted for existing systems in order to present the required foundations for the automated adaptation approach we present in section 4. In section 5, we demonstrate the practical applicability of the tool we have implemented before we compare our approach to related works in section 6. A brief discussion of ongoing work and a summary of our contribution concludes our paper in section 7.
2 Component Software As indicated in the introduction, the decomposition of large systems into more manageable parts is a common approach in software development. Still, the term “software
168
O. Hummel and C. Atkinson
component” (and especially its relation to “software object”) is probably one of the most-discussed terms in software engineering. The first widely accepted definition was formulated at the Workshop on Component-Oriented Programming (WCOP) in 1996 [4]. Its main essence is that components have to hide their implementation behind contractually specified provided interfaces and may have explicit context dependencies (so-called required interfaces) only. However, the debate still continues as there are many open questions left. Today, for example, there are some widely accepted component technologies such as CORBA or EJB available, but interestingly they both use objects (“plain old Java objects” or POJOs in the latter case) as their underlying building blocks and do not fully match the component definition cited above for various reasons. To date, there is no genuine programming construct in object-oriented languages, and thus components can only be mimicked to a certain extend by using packages, (inner) classes and interfaces in order to achieve component-like behaviour. Only recently, some industry-driven efforts – such as OSGi [27] – have tried to mitigate this problem by defining deployment bundles that package together a number of Java classes and come closer to the above component definition. However, we do not want to pursue this discussion at this point - our goal with this brief outline is rather to motivate the understanding of the term that we will use throughout the remainder of this paper: We define a component as a software entity that offers its functionality only through a well-defined interface and thus is composable with other components without the need to disclose its underlying implementation. This definition clearly includes the common notions of objects and (web) services in use today and the ideas introduced in this paper can be applied to them as well as to other forms of modules. In fact, for the sake of simplicity and due to its high profile, we will use Java classes to illustrate our ideas in the following. We will explicitly mention other concepts only where specific differences arise. 2.1 Component Integration Traditionally, one of the main drivers for component-based software development has been the reuse of existing software artefacts [3]. However, this is an area that long suffered from a lack of reusable material that prevented the creation of generally usable reuse systems. Only recently have some innovative approaches such as Test-Driven Reuse [11] taken advantage of the exploding amount of freely available open-source components and become able to deal with millions of components. However, even the latest component retrieval approaches need to live with the fact that increasing complexity of components reduces the likelihood of finding perfectly matching reuse candidates. Broadly speaking, as already explained in the introduction, two different criteria must be satisfied in order to integrate a component into a given environment, namely the component must match the needs of the environment syntactically and semantically. Although this distinction is already sufficient to understand the basic contribution of our approach, the more detailed set of criteria recently provided by Becker et al. [12] makes the goal of our approach clearer and will facilitate better comparability with other ideas later. The authors introduce a finer grained taxonomy that contains five distinct classes of integration mismatches, namely –
Automated Creation and Assessment of Component Adapters with Test Cases
1. 2. 3. 4. 5.
169
Technical mismatches Signature mismatches Protocol mismatches Concept mismatches Quality mismatches
These mismatches are ordered from top to bottom since a mismatch occurring in a higher class makes the consideration of lower classes immediately pointless. In other words, should a technical mismatch occur (i.e. component and target environment use different platforms) it does not make sense to check for a matching signature as the technical mismatch already prevents the components from functioning together. Signature (mis)matches in this classification are widely equivalent to what we described for syntactic matching earlier. Protocol mismatches focus on the invocation order of operations. For example, with a stack data type at least one element must have been pushed on the stack before its pop operation can be called successfully. Generally, these mismatches are a subclass of the semantic mismatches we introduced earlier and are related to concept (i.e. functionality) mismatches in the above classification as both classes are usually described using (semi-)formal pre- and postconditions [13]. However, since full formal descriptions of pre- and postconditions are often difficult if not impossible to check automatically (due to the halting problem), it makes sense to consider protocol matching separately as it can be investigated with simpler notations (such as state machines or petri nets). As the name implies, quality mismatches concentrate on non-functional issues, such as response time or reliability. While some, like response time, might be adaptable by the use of special mechanisms (such as caching in this case), other non-functional constraints such as the latter example are often not adaptable at all. As mentioned before, the goal of the approach we present in this paper is the group of signature mismatches that has been further investigated by Βecker et al. [12]. Based upon Zaremski and Wing’s seminal work on signature matching [14] that we will introduce in more detail in section 3.1, Becker et al. identified a number of potential signature mismatches that need to be supported by an adaptation solution. We will utilize (and further explain) this collection in section 5.1 later in order to assess our prototypical implementation and to allow better evaluation of its capabilities.
3 Foundations of Component Adaptation The adapter pattern described by the Gang of Four [7] as the archetype for adaptation comes in two forms – a static variant called the class adapter which is based on multiple inheritance and a dynamic variant based on delegation known as the object adapter. For today’s most widespread object-oriented languages such as Java and C# that do not support multiple inheritance, the more appropriate variant is the object adapter which we will thus briefly explain in the following. The UML class diagram below depicts a situation where adaptation is required in order to make the Client class on the left-hand side work with the class shown on the right-hand side (the Adaptee). Unfortunately, the Adapatee provides an interface that is different to the specified (Target) interface required by the Client. Hence, the role of the
170
O. Hummel and C. Atkinson
ObjectAdapter class is to implement Target by forwarding the requests it receives from the Client to the appropriate operation of the Adaptee. Ideally, of course, this has to happen transparently to both the Client and the Adaptee. In other words, neither the Client nor the Adaptee is aware of the fact that an adapter is “translating” the requests and responses between them. Obviously, for the sake of simplicity, the Target interface shown in Fig. 1 could be omitted and the Client could use the ObjectAdapter directly.
Fig. 1. Object adapter pattern as envisaged by the Gang of Four
The implementation of the ObjectAdapter class is straightforward. It needs to create an instance of the Adaptee during its own instantiation and forward all incoming requests to it as it executes. Once a response is returned from the Adaptee, it is passed on by the adapter to the Client. The challenge for a tool supposed to create adapters automatically is to figure out the internal “wiring” responsible for the forwarding of the adapter solely based on the interface and contract information provided by the Target and the Adaptee. 3.1 Signature Matching The first fundamental prerequisite required for (automatic) adapter creation is to find out when two interfaces can be regarded as equivalent (or isomorphic) or when there is a chance they can potentially be made equivalent. In the reuse community, this process is usually called signature matching. Signature matching in its original form was defined by Zaremski and Wing [14] for the retrieval of functions and modules in functional languages from a component library and recognizes a match between two functions when they are identical in terms of the types they use in their signatures. In other words, the names of functions and parameters are fully ignored. More formally, this can be expressed as follows: Signature Match(q, M, C) = {c ∈ C : M(c, q)} This means, a signature match requires a given query signature q, a match predicate M and a component library C in order to deliver a set of components where each one satisfies the match predicate. The signature of a function is definied as the list of types used as the function’s input and output parameters and the exceptions it can throw. In
Automated Creation and Assessment of Component Adapters with Test Cases
171
addition to simple function matches, Zaremski and Wing also investigated module matches where a module is seen as a multiset of functions exhibited in the interface of the module. To our knowledge, only [15] has transferred these ideas to an objectoriented language, namely Ada, by condensing a collection of operation signatures into an object abstraction. We are not aware of any work in this direction for today's widely used object-oriented languages such as Java and C#. However, it is fairly straightforward to also apply these ideas to today’s object-oriented languages and components as well as services. Clearly, it is not necessary for the desired interface and the adaptee to be absolutely isomorphic, it is obviously sufficient if all operations of the adapter can be mapped to one operation of the adaptee; there can still be unused operations in the latter. However, it often happens that an operation signature can appear more than once within an adaptee which is a challenge not solvable purely by the means of signature matching anymore. Although the names of the operations might help in a case like this, in practice, there are often situations where establishing the right match becomes a tedious task even for a human developer. Consider, for example, the case in which operations are not well documented or not even well named (as is today often the case with web services). Further ideas developed by Zaremski and Wing include the use of so-called relaxed signature matches that, for instance, also allow different parameter orders to be accepted. Likewise, the idea of “relaxing” parameter or return types used with functional languages is also applicable for primitive types in object-oriented languages today. The general rule there is that preconditions cannot be strengthened and postconditions cannot be weakened for a subtype. Translated to parameters in operation signatures, this means that the “range” of a parameter in a reuse candidate can be increased (e.g. a long parameter on the adaptee side can also accept an int from the client or some reference type parameter can also accept objects of a subtype). Clearly, the inverse principle is valid for return types. For object types this can be based on the well-known Liskov Substitution Principle [16]. In order to conclude this subsection, we want to reiterate that signature matching is only able to determine whether two operation signatures can be considered equal, which is, of course, an important prerequisite for adaptation. However, it cannot be used to determine whether two operations are semantically adaptable or to derive the required mapping of operations and their parameters for the adaptation itself. We will discuss how to deal with this challenge in the next section.
4 Automating Adaptation In this section we explain how the appropriate counterpart for a desired operation can be automatically identified in a candidate component. In other words, the challenge that we address here is finding the “correct” way of mapping the operations and the parameters of the desired component to those in an adaptee component. This is essentially a two-stage process: First, based on signature matching, all potentially correct counterparts (i.e. all syntactically matching operations) need to be found. Details of the algorithms that create all valid permutations of the operation and parameter mappings are explained in the next subsection. Second, once all potential mappings have been established it is
172
O. Hummel and C. Atkinson
necessary to find the correct mapping for the adapter amongst the created permutations. For this purpose, it is necessary to have a specification of the functionality expected by the client at hand. However, specifying the functionality of software components is difficult and has consequently been an area of intensive research for decades. The commonly accepted approach today is the use of contracts [13] that specify pre- and post-conditions of operations in some typically (semi-) formal way. However, developers often perceive this as cumbersome since they need to learn an additional specification language and thus contracts are rarely used in practice. In Java, for example, this situation has been recently alleviated with the introduction of assertions that allow expressing pre- and postconditions in Java syntax. Nevertheless, due to the halting problem, the checking of assertions still requires the execution of code with concrete input values and is thus and closely related to the following idea from the reuse community. There, Podgurski and Pierce came up with the idea of using so-called samples (i.e. tuples of input and expected output values) to check the semantic fitness of operations [10]. About a decade ago, the test-driven development movement popularized the similar approach of using test cases created prior to the actual implementation as a specification for the required functionality [9]. Test cases have recently also been used successfully to implement so-called test-driven reuse [11] where they are used to evaluate the semantic fitness of reuse candidates. In fact, our adaptation solution was largely motivated by the need to adapt reusable Java classes in the context of our research in that area. Although test cases in this context naturally do not guarantee a full semantic assessment of the tested component, we have found them being a viable candidate for assessing the quality of adapters as we will demonstrate and discuss in section 5. 4.1 Permutation Creation As indicated before, the first step required by our automated adaptation process is the creation of a table containing all possible syntactical adaptations for a given adaptee class and the desired interface of a client. Essentially, this is a four stage process based on two algorithms explained in the following. First, signature matches need to be established between all methods of the adapter and all matching methods of the candidate (i.e. the adaptee) according to the following Algorithm 1. Algorithm 1. Discovering feasible method mappings for each method in the adapter initialize empty List listm of method mappings for each method in the candidate if signatures match add method mapping to listm endif endfor endfor For a better understanding of the algorithm (and the later evaluation of our prototypical implementation) we illustrate its application by using an adaptation challenge for a component performing simple mathematical matrix calculations. It is inspired by
Automated Creation and Assessment of Component Adapters with Test Cases
173
Fig. 2. An exemplary adaptation challenge
an evaluation example used in [26] and illustrated in the figure below. The interface of the required matrix component is shown on the left-hand side and the one provided by the adaptee on the right-hand side. For the sake of brevity we will omit a few methods in the following and merely consider the set and mul methods as they are sufficient to demonstrate the main challenges. As identified in previous work [17] the “translation” of a Matrix into a MatrixAdaptee required by the mul method is another challenge for adapter creation. We will discuss this issue in some more detail in section 5.1 and just assume it as solved for now. Thus, after Algorithm 1 has been executed, listm will contain the following entries: set mul mul mul
→ → → →
setCell add mult sub
Here and in the following we use the right arrow to indicate an “is forwarded to” relationship. In other words, e.g. the set operation of the adapter (Matrix) forwards the request to setCell in the adaptee (MatrixAdaptee). Once these individual mappings have been established, they need to be combined for all methods contained in the Matrix component according to the algorithm shown below. Algorithm 2. Combining method mappings for the whole class initialize empty List list1 of combinations for each method in the adapter initialize empty List list2 of combinations for each mapping in the listm for each entry in list1 or once if empty if candidate method not used in list2 so far add method mapping to list2 endfor endfor list1 = list2 endfor
174
O. Hummel and C. Atkinson
An important constraint in Algorithm 2 is that no method of the candidate may be addressed twice by adapter operations, which cannot happen in this simple example, however. The following list contains the three independently feasible internal wirings for the adapter class as obtained from the application of Algorithm 2: set → setCell + mul → add set → setCell + mul → mult set → setCell + mul → sub Furthermore, for each method, these mappings have to be combined with the feasible parameter permutations which can be derived in the next two stages of the permutation creation process using the same principles described in the two algorithms above. First, for each method mapping a list is created identifying which parameter in the adapter’s method can be mapped to which parameter in the candidate method, e.g. for the set/setCell mapping: set(int row, int col, double val) → setCell(int i, int j, double v) This yields: row row col col val
→ → → → →
i j i j v
(int → (int → (int → (int → (double
int) int) int) int) → double)
This list needs to be combined appropriately under the constraint that no parameter is used twice per method adaptation so that the resulting list of combinations has the following form: set(row, col, val) → setCell(row, col, val) set(row, col, val) → setCell(col, row, val) Finally, we need to combine all method adaptations with their appropriate parameter permutations, which, for our example, leads to a total of twelve possible combinations of adaptations like the following: set(row, col, val) → setCell(row, col, val) mul(m) → add(m) and set(row, col, val) → setCell(col, row, val) mul(m) → add(m)
Automated Creation and Assessment of Component Adapters with Test Cases
175
And so on with mul(m) → mult(m) and mul(m) → sub(m). Once all potential adaptations have been created like this, one configuration after the other can be checked for its fitness with the help of ordinary unit test cases typically created by the developers for the validation of a system’s components anyway. We will explain this evaluation process in more detail in the next section.
5 Proof of Concept Implementation The naive way to assess the adaptations created by the above algorithms with the help of test cases would be to submit an adapter class for each potential mapping to a testing environment along with the test case and the candidate class. However, this would involve a huge overhead since for every permutation a new adapter needs to be created, compiled, transferred, and executed. A more efficient solution that uses Java’s reflection capabilities to lower the overhead to just one compilation run can be implemented as described in the following. The central idea is to not create new adapters at compile-time, but to interpose the permutation engine (the Permutator object in figure 3 below) in between the adapter and the candidate at run-time. This allows the switching to a new mapping within the adapter to happen more efficiently. The basic flow and the participants of this process are shown in the following sequence diagram and are explained in more detail thereafter.
Fig. 3. Sequence diagram of the testing process
The TestCoordinator object on the left-hand side is responsible for managing the whole adaptation and testing process. Upon its invocation, it initializes the Permutator object and lets it create a lookup table that stores all possible permutations for the method and parameter mappings derived from the interface of the Adapter and Candidate (i.e. the adaptee) objects. After that, the engine is set up to carry out the permutation and testing cycle by executing the TestCase, which is a normal
176
O. Hummel and C. Atkinson
JUnit test case. In order to provide the TestCase with the “illusion” of having an appropriate class under test, as discussed before, the Adapter is created according to the interface specified in the TestCase. The Adapter object, in turn, is created with the knowledge of the Permutator object and does not directly call the Candidate (i.e. the adaptee) as an adapter usually would, but rather forwards the parameters and an ID of the invoked method to the Permutator. The Permutator internally keeps track of the state of permutations and is thus able to look up the relevant internal wiring for the current testing iteration. This allows it to invoke the actual operation of the Candidate with the appropriate parameter permutation. For the sake of clarity, we have depicted this scenario with just one invocation of the Candidate in figure 3. Of course, in real life, this needs to be done for every call from the TestCase to the Candidate – in other words, for one complete execution of the test case – with the same settings. As soon as one of these tests fails, the engine assumes that the current adaptation is not correct and a new permutation needs to be adjusted. Accordingly, the TestCoordinator notifies the Permutator to switch to the next permutation and the TestCase is executed once again. This surrounding loop is executed until either the complete test case has been passed without error or no further permutations are available. The former case obviously occurs for semantically acceptable reuse candidates and a correct adaptation while the latter indicates that the candidate is for some reason not reusable in the given context. This usually means that it does not offer the required functionality. 5.1 Evaluation In order to assess the capabilities of our prototype we designed a complex adaptation challenge for a comprehensive “in vitro” evaluation. The two interfaces shown in figure 4 below contain the adaptation aspects currently supported by our system, namely – a constructor with a parameter that needs to be stored in an object variable, one method that accesses this variable, one method that changes this variable and various methods with multiple parameters. For this task, we have defined a simple JUnit test that specifies the interface of the class shown on the left-hand side of the figure and prepared an adaptee with the interface shown on the right-hand side. In order to make the challenge more expressive, the signature of each method appears twice to demonstrate that the tool is not only capable of finding the correct order of parameters, but identifying the correct operation as well. The “doNothing” operations contained in the adaptee are those that are meant to return a value that leads to a failed test. In total this challenge yielded 12,288 possible permutations and the small blue digits in figure 4 indicate the amount of possible permutations per method for the given example. The correct permutation was the 1,544th, which was discovered after roughly seven and a half minutes of our test system, which was a 2.0 GHz single-core notebook with 1.5 GB RAM running Windows XP. This example also covers the most relevant challenges for an adapter generator based on the types of mismatched recognized by Becker et al. [12]. The table following below lists each one, shows how far our prototype supports it, refers to an example adaptation from the above challenge and contains a brief explanation for each.
Automated Creation and Assessment of Component Adapters with Test Cases
177
Fig. 4. Adaptation challenge built to check the features of prototype implementation Table 1. Overview of adaptation challenges and how far they are supported by the prototype implementation
Mismatch
Supported
Example
Brief Explanation
1. Naming of methods
yes
noParam -> check
through evaluation of possible permutations
2. Naming of parameters
implicitly
add -> adder
with permutations
3. Naming of types
only for the adaptee itself indirectly
Calculator -> CalculatorAdaptee used types can be adapted separately
used types are identical but have different names used types require adaptation as well
4. Structuring of (used) complex types 5. Naming of exceptions
no
6. Typing of methods
no
7. Typing of parameters
no
8. Typing of exceptions
n.a.
9. Ordering of parameters 10. Number of parameters
yes no
sub -> subtractor
11. Return values of own type 12. Parameters of own type
yes yes
create -> creator test -> tester
exceptions with a different name can be adapted returned values can be of a subtype submitted parameters can be of a subtype essentially identical with 5 in Java through permutations number of parameters can vary e.g. due to constant or empty parameters see above see above
Rows 11 and 12 are not contained in the reference publication and have been added by us. They are referring to the previously mentioned “translation problem” that occurs when a class uses objects of its own type as parameters or return values such as the test method in figure 4 (or the mul operation from figure 2). Since the CalculatorAdaptee expects an object of its own type it is not possible to simply forward the calculator instance in this case. Rather it must be replaced by the adapter with the appropriate adaptee instance. A solution for this issue is discussed in a previous publication [17] in more detail.
178
O. Hummel and C. Atkinson
In addition to the example constructed above, we also applied our system to the matrix adaptation challenge from figure 2 after we had created a simple test case for it. Out of 24 feasible permutations the correct one was chosen in less than five 5 seconds. In order to have our system undergo another more practically relevant evaluation we used some further Matrix components we had previously retrieved from the merobase.com component search engine in summer 2007. In total, this set comprises 137 potentially reusable candidates out of which 10 have exactly identical method names with the interface we specified on the left-hand side of figure 2. Out of these 10 components only two candidates could be directly executed without adaptation or any other modification. Of course, our test case did not even compile successfully with the other 127 candidates due to deviating class or operation names. However, once we included our automatic adaptation creation into the testing process, we were able to test 26 out of these 137 candidates successfully. No false positives were detected amongst them in a manual inspection. There were in fact some false negatives, i.e. classes that seem to offer the right functionality but which our prototype was not able to adapt. The main reasons for this were lacking dependencies and thrown exceptions that our tool is not able to adapt yet. For adapting and assessing all 137 components our prototype requires about 5 minutes and 30 seconds on our test system. 5.2 Discussion As the above evaluations reveal, the automated adaptation engine described in this paper not only works in a controlled laboratory environment, but also demonstrated its robustness with real world reuse candidates arbitrarily downloaded from the web. In this context it considerably increases the probability of finding appropriate reuse candidates. Interestingly, the applicability of automatic adaptation goes far beyond plain reuse. For example, it seems feasible to extend recent research on self-testing components [22] with an automated adaptation engine and thus to create self-adapting components [24] that can be used in dynamically reconfiguring systems. However, in order to allow the application of our approach in a practical environment with perhaps even more complex components, we still need to overcome a number of challenges. As mentioned before, it is obvious that test cases are by no means a complete specification of the behaviour of components. They can merely sample it and the reliability of reuse candidates retrieved with a test-driven reuse system and adapters generated by our prototype is of course closely correlated with the quality of the tests employed. Our previous experience with test-driven reuse [11] nevertheless indicates that already quite simple test cases created with common practices rule out false positives with high confidence. Clearly, establishing concrete measures and guidelines in this context is another interesting area for future research. In order to conclude the discussion of our approach we briefly need to come back to the initial comparison of components and objects as this is certainly an important issue for its scalability. As we have demonstrated in the evaluation section, our prototype is able to adapt the interfaces of classes with significant complexity and since a well defined component is supposed to hide its implementation behind an interface of identical style our approach is applicable in that case as well. However, as our current “in vitro” implementation is still based on a brute-force assessment of all possible adapters its application can become time consuming with increasing interface sizes.
Automated Creation and Assessment of Component Adapters with Test Cases
179
Thus, we are currently exploring potential optimization strategies for the testing process. Currently, the most promising idea to speed up the permutation evaluation is reusing test results of already tested adapter configuration for operations. In other words, the tool remembers previous test results and therefore does not need to process the adaptation of an operation again once it has been tested with the same adaptee method and parameter permutation. However, as their might exist subtle dependencies between operations we still assume that we need to have a fallback to the brute force variant in order to be sure not to miss a working adapter configuration. Nevertheless, we expect this solution will improve the scalability of our approach considerably. Scalability, however, is often confused with the ability to deal with more than one adaptee at a time in the context of components and class assemblies. However, adaptation per se is defined as a one to one mapping between adapter and adaptee [7] and composing a number of classes or components beyond that notion is rather an orchestration challenge (based on the facade pattern [7]) as currently under intensive investigation in the web service community. Nevertheless, the approach just presented might be helpful to find a general solution to this group of problems as well, but this has yet to be investigated as well.
6 Related Work We have already referred the reader to [8] for a comprehensive overview of general adaptation techniques. This article lists a tremendous amount of literature that offers a wide variety of approaches for the integration of components into a system from a large number of communities. We, however, focus on those previous approaches that aimed to automate the adaptation process in the remainder of this subsection. To our knowledge, Penix and Alexander [18] were the first researchers that sketched a solution for this challenge. They grounded their proposal in formal component specifications. However, they merely described some theoretical foundations, but provided neither concrete algorithms nor a practical implementation. More recently, Haak et al. [19] proposed a similar approach for automated adaptation of a subset of Standard ML and claim to have a working solution for simple modules enriched with machine-readable semantic specifications. However, neither a proof of concept nor an evaluation is provided. Furthermore, such semantic specifications impose additional effort on the developers and are not likely to be created for reusable components. Bracciali et al. [20] developed a methodology that comprises a small language for adapter specifications from which adapters can be automatically derived. However, it suffers from a similar shortcoming as the previous approaches since the specification of the adapters needs to be figured out by a human developer. Gschwind has also worked on the automation of component adaptation and proposed the use of an adapter repository where adapters can be stored and selected automatically [21]. However, the content of the repository (i.e. the adapters) need to be generated by humans again. Other more recent efforts to automate adaptation such as [23] or [25] also present interesting ideas, supporting the semi-automated generation of adapters for web services or the automated creation of adaptation contracts, but neither of them is able to fully support the whole process of adapter creation without human support.
180
O. Hummel and C. Atkinson
7 Conclusion The wide variety of existing articles discussing the automation of component adaptation demonstrates the importance of this topic. However, to the best of our knowledge, so far there exists no approach with appropriate tool support that would be able to automatically deliver practically usable syntactic adaptations for components in popular mainstream programming languages such as Java. We have experienced the necessity of such a technology during research we conducted for a test-driven component retrieval system and found that the underlying testing engine could also be used to assess the quality of automatically created adapters. Thus, we developed a so-called permutation engine that is able to derive all syntactically feasible adaptations between a specified interface and a syntactically mismatching component. Together with the testing engine this yields a system that is usually able to generate a working adapter for average size components completely without human intervention in just a few seconds. No previous approach has been able to offer such a large degree of automation including syntactic and protocol adaptation as our test-driven adaptation engine. Another big advantage of our approach is the fact that it is solely based on artefacts (namely the test cases) that are normally created during the development process of a system and does not require any additional specification effort or the learning of a potentially complex formal specification language. Together with performance optimizations and coverage improvements currently under development it opens a host of interesting research possibilities that, in the future, promise to facilitate not only the composition of complex applications from components, but also the orchestration of web services.
References 1. Brooks, F.P.: No silver bullet: Essence and accidents of software engineering. IEEE Computer 20(4) (1987) 2. Parnas, D.L.: On the Criteria to be Used in Decomposing Systems into Modules. Communications of the ACM 15(12) (1972) 3. McIlroy, D.: Mass-Produced Software Components. In: Software Engineering: Report of a Conference Sponsored by the NATO Science Committee, Garmisch, Germany (1968) 4. Szyperski, C.: Component Software, 2nd edn. Addison-Wesley, Reading (2002) 5. Erl, T.: Service-oriented architecture: concepts, technology and design. Prentice-Hall, Englewood Cliffs (2005) 6. Crnkovic, I., Chaudron, M., Larsson, S.: Component-based Development Process and Component Lifecycle. In: Proc. of the Intern. Conf. on Software Engin. Advances (2006) 7. Gamma, E., Helm, R., Johnson, R., Vlissides, J.: Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, Reading (1995) 8. Kell, S.: A Survey of Practical Software Adaptation Techniques. Journal of Universal Computer Science 14(13) (2008) 9. Beck, K.: Extreme programming explained: embrace change. Addison-Wesley, Reading (2004) 10. Podgurski, A., Pierce, L.: Retrieving Reusable Software by Sampling Behavior. ACM Transactions on Software Engineering and Methodology 2(3) (1993)
Automated Creation and Assessment of Component Adapters with Test Cases
181
11. Hummel, O., Janjic, W., Atkinson, C.: Code Conjurer: Pulling Reusable Software out of Thin Air. IEEE Software 25(5) (2008) 12. Becker, S., Brogi, A., Gorton, I., Overhage, S., Romanovsky, A., Tivoli, M.: Towards an Engineering Approach to Component Adaptation. In: Reussner, R., Stafford, J.A., Szyperski, C. (eds.) Architecting Systems with Trustworthy Components. LNCS, vol. 3938. Springer, Heidelberg (2006) 13. Meyer, B.: Applying Design by Contract. IEEE Computer 25(10) (1992) 14. Zaremski, A.M., Wing, J.M.: Signature Matching: A Tool for Using Software Libraries. ACM Transactions on Software Engineering and Methodology 4(2) (1995) 15. Stringer-Calvert, D.W.J.: Signature Matching for Ada Software Reuse. Master’s Thesis, University of York (1994) 16. Liskov, B., Wing, J.M.: A behavioral notion of subtyping. ACM Transaction on Programming Languages and Systems 16(6) (1994) 17. Hummel, O., Atkinson, C.: The Managed Adapter Pattern: Facilitating Glue Code Generation for Component Reuse. In: Edwards, S.H., Kulczycki, G. (eds.) ICSR 2009. LNCS, vol. 5791. Springer, Heidelberg (2009) 18. Penix, J., Alexander, P.: Towards Automated Component Adaptation. In: Proceedings of the International Conference on Software Engineering and Knowledge Engineering (1997) 19. Haack, C., Howard, B., Stoughton, A., Wells, J.B.: Fully automatic adaptation of software components based on semantic specifications. In: Kirchner, H., Ringeissen, C. (eds.) AMAST 2002. LNCS, vol. 2422. Springer, Heidelberg (2002) 20. Bracciali, A., Brogi, A., Canal, C.: A formal approach to component adaptation. The Journal of Systems and Software 74(1) (2005) 21. Gschwind, T.: Adaptation and Composition Techniques for Component-Based Software Engineering, PhD thesis, Technical University of Vienna (2002) 22. Brenner, D., Atkinson, C., Malaka, R., Merdes, M., Suliman, D., Paech, B.: Reducing Verification Effort in Component-Based Software Engineering through Built-In Testing. In: Information Systems Frontiers, vol. 9(2). Springer, Heidelberg (2007) 23. Motahari Nezhad, H.R., Benatallah, B., Martens, A., Curbera, F., Casati, F.: Semiautomated adaptation of service interactions. In: Proceedings of the International Conference on the World Wide Web (2007) 24. Atkinson, C., Hummel, O.: Reconciling Reuse and Trustworthiness through Self-Adapting Components. In: Proceedings of the International Workshop on Component-Oriented Programming (2009) 25. Martin, J.A., Pimentel, E.: Automatic Generation of Adaptation Contracts. Electronic Notes on Theoretical Computer Science, vol. 229, p. 2 (2009) 26. Czarnecki, K., Eisenecker, U.: Generative Programming: Methods, Tools, and Applications. Addison-Wesley, Reading (2000) 27. O.S.G. Alliance: OSGi Service Platform Core Specification. Release 4. OSGi (2007)
An Empirical Study of the Component Dependency Resolution Search Space Graham Jenson, Jens Dietrich, and Hans W. Guesgen School of Engineering and Advanced Technology Massey University New Zealand [email protected], {J.B.Dietrich,H.W.Guesgen}@massey.ac.nz
Abstract. Dependency Resolution (DR) uses a component’s explicitly declared requirements and capabilities to calculate systems where all requirements are met. DR can lead to large amounts of possible solutions because multiple versions of the same component can be available and different vendors can offer the same functionality. From this set of potential solutions DR should identify and return the optimal solution. Determining the feasibility of many optimisation techniques largely depends on the size and complexity of the DR solution search space. Using two sets of OSGi components collected from the Eclipse project and Spring Enterprise Bundle Repository, we measure the size and examine the complexity of the DR search space. By adding simple constraints based on desirable properties, we show the potentially large search space can be significantly restricted. This restriction could be used to make more complex optimisation algorithms feasible for DR.
1
Introduction
Most modern component models require components to explicitly state what they require and provide, usually within attached meta-data, for the necessary composition and execution of those components. Dependency Resolution (DR) takes advantage of this information by calculating the relationships between components and defining a system where all requirements are satisfied. This can be used at design time to determine the required dependencies to build and test a project (as in Apache Maven [1]), at runtime to evolve or extend a component based system (as in Eclipse P2 [2]), or it can be used to build and restructure software product lines [3]. The main advantage of DR is lowering costs of building, maintaining and evolving systems, it is therefore seen as a key piece of functionality in modern component systems. For instance, consider the following scenario: A component c1 requires a logging service, and the components c2 and c3 provide such a service. Therefore c1 depends on c2 or c3 , or c1 → c2 ∨ c3 using the notation from [4]. If a user wished to install c1 , they could use DR to calculate the solutions where all requirements are met. DR identifies the possible combinations ({c1 , c2 }, {c1 , c3 } or {c1 , c2 , c3 }) to return to the user for installation. L. Grunske, R. Reussner, and F. Plasil (Eds.): CBSE 2010, LNCS 6092, pp. 182–199, 2010. c Springer-Verlag Berlin Heidelberg 2010
An Empirical Study of the Component DR Search Space
183
Finding any solution using DR is non-trivial but often manageable. As components are developed for practical use, the complexity of their dependencies are far from the worst case scenario. However a potentially enormous amount of possible solutions exists, and as each solution has different properties the problem of selecting one particular solution is an optimisation problem. The resolved solutions can be optimised for many different attributes including vendor preferences, quality, licence and any aspect that effects a solution’s desirability or functionality. A user could have strict or qualitative constraints on component memory usage as the user’s system may run in a resource constrained environment. For our above example, if the user’s system has limited memory, they would prefer a logging service component that uses the least memory. If component c3 uses less memory than c2 , the optimum solution is {c1 , c3 }. Our main contribution in this study is an empirical investigation to measure the search space of DR using different constraints. We first describe our motivations in Section 2, followed by definitions of our component model and DR function in Section 3. In Section 4 we discuss the implementation and mapping of our DR function to a boolean satisfaction problem (SAT). We start our empirical investigation in Section 5 by discussing our two datasets from the OSGi component model, the Spring Enterprise Bundle Repository (SEBR) and the core Eclipse Project update sites. Using these datasets we empirically show in Section 6 that although DR potentially has a large search space, making optimisation impractical, using additional constraints we can significantly restrict the search space. We finish discussing the future extensions, related work and conclusions of this study.
2
Motivation
DR is an important aspect of component systems as it lowers the overall cost of maintaining, evolving and extending them. When a resolved solution is returned it is often used without user validation, as the solution may be large, complex and difficult to understand without detailed analysis. The users must assume the resolved solution to be optimal for their needs, which is not necessarily the case. Current methods of DR optimisation are lacking, therefore figuring out the feasibility of additional optimisation algorithms is necessary. A common DR approach to selecting an optimal solution is to maximise the versions of components and capabilities and minimise the number of components, e.g. Eclipse P2 [2]. The problem with the first heuristic is that the version information does not tell us what quality improves, how much it improves or if that quality is relevant to the user or system. It also cannot compare two capabilities from different vendors as their versions are unrelated; an inferior component could be many versions higher than a superior component, if from different vendors. The second heuristic optimises for less components in a solution which can be against many component system goals, like reuse or extensibility. For example, this heuristics optimal solution would be a monolithic component with redundant functionality, over a set of more specific components.
184
G. Jenson, J. Dietrich, and H.W. Guesgen
Current implementations of DR can ignore other requirements from the user, the system context or the combination of components. Contextual constraints from the user, such as those collected from system requirements, customise the solution to user and system context. The cohesion of a set of components is also of great importance to a systems functionality and smooth operation. Although components may interact correctly, there may be friction [5] resulting in non fatal errors or excessive log warnings, an overall detriment to a system. The motivation for this study is to show that it is feasible to improve DR using better optimisation techniques than currently used. Measuring the DR search space can be used in determining the feasibility of many optimisation techniques. The search space of DR is potentially large, therefore we offer means to limit this space through additional constraints. This opens up the doors for more advanced optimisation techniques to identify the optimal solution.
3
Component Model and Dependency Resolution Definition
In this section we provide a definition of a simple component model that allows us to formally define a DR function. We define our own component model as opposed to using a currently existing one to simplify and focus on DR and to make it more communicable and understandable. This definition is not a replacement for a functional component model like Fractal [6] or SOFA [7]; however these concepts used can be easily mapped to such models. 3.1
Component Model
Our component model contains the pairwise disjoint sets of components (C), requirements (R) and capabilities (P). A composite κ is a set of components, κ ∈ 2C . The core relationships between these three sets are requires, provides and satisf iedBy. The relationships requires and provides are the relationships between a component and its requirements and capabilities, respectively. These two relationships are created from explicitly stated information within the components meta-data as opposed to satisf iedBy which is derived or calculated. A requirement is satisf iedBy a capability when it matches the requirements constraints, this is defined in the underlying component model, and therefore does not belong on this layer of abstraction. requires ⊆ C × R provides ⊆ C × P satisf iedBy ⊆ R × P These three relationships are used to define the depends relationship, which is between a component (cr ) with a requirement (r) for a satisfactory capability (p) that a component (cp ) provides. (cr , r, p, cp ) ∈ depends ⇔ (cr , r) ∈ requires and (r, p) ∈ satisf iedBy and (cp , p) ∈ provides
An Empirical Study of the Component DR Search Space
185
This relationship can be represented as a dependency graph DG = (V, E) that is a directed graph where V is a set of components, and E is defined by the depends relation (where cr is the source vertex, cp is the target vertex and (r, p) is the label of the edge defined by the dependency (cr , r, p, cp )). We later use the DG and its metrics to study our data sets, as it is a conceptually intuitive way of representing dependencies. These relationships can also be composed into a binary transitive closure relationship depends+ : (provides−1 ◦ satisf iedBy ◦ requires)+ (noted as the composition of binary relations), where (c1 , c2 ) ∈ depends+ defines the relationship between two components where c1 is connected by a path to c2 , that is to say that if c1 is included in the solution it may require the inclusion of c2 . 3.2
Dependency Resolution
We define DR as the function DR : 2C → 2C , which returns a set of components from a query set of components. The four constraints that define DR are: reachability, the query must potentially depend upon all components (excluding the query) in the solution (1); a returned solution must contain the query (2) (the extensive property); completeness, all components in the solution must have all their requirements filled by a capability of another component in the solution (3); and cohesion, all components in the solution (excluding the query) must be depended upon by another component in the solution (4). ∀c ∈ (DR(κ)\κ)∃cq ∈ κ : (cq , c) ∈ depends+ (reachability) ∀κ : κ ⊆ DR(κ) (extensive)
(1) (2)
∀cr ∈ DR(κ)∀(cr , r) ∈ requires∃cp ∈ DR(κ) : (cr , r, p, cp ) ∈ depends (completeness)
(3)
∀cp ∈ (DR(κ)\κ)∃(cr , r, p, cp ) ∈ depends : cr ∈ DR(κ) (cohesion)
(4)
We further assume that the DR can be decomposed into two functions (5), the optimisation function (σ) and the solution search space (DR∗ ). This separation is to investigate the search space of DR, while leaving the optimisation function unspecified. This is the basis of a framework where optimisation is possible, however further definition and study of this optimisation is left for future work. DR := σ ◦ DR∗ C
σ : 22 → 2C ∗
C
DR : 2 → 2 3.3
2C
(5) (6) (7)
Example
To illustrate this, let’s consider the following example that has four components C = {c1 , c2 , c3 , c4 }, with the dependencies depends = {(c1 , rc1 , pc2 , c2 ), (c1 , rc1 , pc3 , c3 ), (c3 , rc3 , pc4 , c4 )}, or c1 depends on c2 or c3 and c3 depends on
186
G. Jenson, J. Dietrich, and H.W. Guesgen
Fig. 1. Example Dependency Graph with possible results
c4 . The DG for this example is shown in Figure 1, with the query {c1 } we can now execute DR. If DR had no constraints, the search space then would be all 16 different combinations of the four components. By adding the reachability constraint (1) all superfluous components are removed from inclusion in the solutions. This restricts the solution space of the query {c1 }, however if the query were {c3 } then 12 of the 16 different solutions, those including c1 or c2 , would be removed as neither are within the transitive closure relation depends+ of c3 . The extensive constraint (2) reduces the search space to 8 possible solutions, those containing the query c1 . The completeness constraint (3) then limits it to 4 potential solutions where all requirements are satisfied; those shown in Figure 1 and the solution {c1 , c2 , c4 }. Finally by adding the cohesion constraint (4) we eliminate the potential solution {c1 , c2 , c4 } because the component c4 is not in the query or required by another component, making the search space the three possible solutions in Figure 1, DR∗ = {{c1, c2}, {c1, c3, c4}, {c1, c2, c3, c4}}. The optimisation function σ is then responsible for selecting a solution from DR∗ .
4
Methodology and Implementation
The DR function is implemented by converting our constraints into a boolean satisfaction problem (SAT); a problem determining if a given boolean formula can be satisfied. This process is briefly described by Le Berre and Parrain [4]
An Empirical Study of the Component DR Search Space
187
and uses a similar process to the Eclipse P2 [2] implementation. We use the SAT4J [8] boolean satisfaction problem solver to identify solutions because SAT solvers provide performance and reliability as they are directly compared and benchmarked for speed and correctness in competitions such as SAT Live [8]. There is much research into the efficiency of solving SAT problems implemented in SAT4J, this efficiency is one reason we use SAT for the resource intensive DR function. SAT4J has been used for many purposes, including formal verification [9], feature models [10], and other dependency resolvers [2]. 4.1
SAT Encoding
To use the SAT solver we must first convert our DR constraints into a SAT problem, we continue with our example from Figure 1. Step 1: The reachability constraint (1) identifies components that the query, directly and indirectly, depends on by doing an exhaustive search of the DG starting from the query components, and any component found is then added to the restricted set. In our example all components are included in this set. Step 2: All components in that set are then encoded into a SAT problem by representing each of them as a boolean variable, where if a component is true it is included in the solution. Therefore each component in our example (c1 , c2 , c3 , c4 ) represents a boolean variable, where this variable is true if and only if its corresponding component is in the solution. In future we use the name of the component to refer to that variable. Step 3: The extensive constraint (2) is encoded by stating all query components as true axioms, i.e. c1 = true. Step 4: The completeness constraint (3) is encoded using a conjunction of co-Horn clauses. For all requirements of each component ci , which is satisfied by capabilities of components c1 . . . cn , a clause is created that states ci → c1 ∨ . . . ∨ cn , this is equivalent to ¬ci ∨ c1 ∨ . . . ∨ cn . In the example, c1 ’s requirement is encoded as ¬c1 ∨ c2 ∨ c3 , and c3 ’s requirement is encoded as ¬c3 ∨ c4 . Step 5: The cohesion constraint (4) is encoded as follows: For all components c0 we consider the set of all clauses where c0 appears in the clause head, i.e. cH i → . . . ∨ c0 ∨ . . . for i = 1 . . . N . If this clause set is non-empty then we add H a new clause c0 → cH 1 ∨ . . . ∨ cN . In our example this would lead to c2 → c1 , c3 → c1 and c4 → c3 . If, for example, c2 also depended upon c4 , the constraint would then be c4 → c2 ∨ c3 . The set of constraints for our example are: c1 and ¬c1 ∨ c2 ∨ c3 and ¬c3 ∨ c4 and ¬c2 ∨c1 and ¬c3 ∨c1 and ¬c4 ∨c3 . This problem is therefore a conjunction of co-Horn clauses, which has been shown for SAT problems to be of P complexity [11]. Search space cardinality (|DR∗ |) is measured by calculating the number of possible models that exist within the stated constraints. The cardinality of the example search space is 3, the solutions shown in Figure 1. 4.2
Mapping to OSGi
To use the DR function in the context of the OSGi component model, it has to be mapped to this component model. To do this, we have to instantiate the
188
G. Jenson, J. Dietrich, and H.W. Guesgen
depends relationships using meta-data extracted from OSGi bundles. OSGi [12] has been selected for this purpose because of its strict specification, relevance to industry, and several large, active and accessible repositories of components, whose meta-data can be extracted and processed. OSGi is a mature component model from the OSGi Alliance that has seen a recent up-take in industry [13]. The core components in OSGi are called bundles; these can provide and require other bundles and packages (Java modules of code). This is accomplished through meta-data attached to the component, shown in Figure 2, with the tags such as Require-Bundle, Import-Package and Export-Package. These requirements can also have associated version-ranges, shown in Figure 2 by the Import-Package tag, written using mathematical interval notation [12]. A bundle or package that fills a requirement with a version range must be of a version within that range. Bundle-Name: TestBundle Bundle-SymbolicName: TestBundle Bundle-Version: 1.0.0 Bundle-Vendor: Graham Jenson Require-Bundle: org.eclipse.core.runtime, Import-Package: org.osgi.framework, org.osgi.util;version="[2.0.0,3.0.0)" Export-Package: org.tb;version="1.0.0" Fig. 2. OSGi Bundle Manifest
To map OSGi to our component model we extend the satisf iedBy relationship to the OSGi specification [12]. The first constraint is checking that the name-space of the requirement is equivalent to the name-space of the capability. The name-space, referred to as ns in (8), is the combination of the type (bundle or package) and the unique identifier (package name or bundle name) of the requirement or capability. The second constraint on satisf iedBy is the selection of a capability within a specified version range. All versions are in the totally ordered set V, as each version is comparable to another. A version range, VR ∈ 2V , is a non-empty set of versions defined by intervals [12]. A requirement with a version range can only be satisf iedBy a capability within that version range. A requirement relates to a version range via the function vr : R → VR , and a capability to a version via v : P → V. These two constraints are seen in (8). (r, c) ∈ satisf iedBy ⇔ ns(r) = ns(c) and v(c) ∈ vr(r) 4.3
(8)
Testing the Dependency Resolver
To test our implementation we first created a test suit describing many problems similar to the one shown in Figure 1. We also used our datasets (discussed in
An Empirical Study of the Component DR Search Space
189
Section 5) and singleton queries (DR(κ) where |κ| = 1) to test our DR implementation by selecting 20 random solutions of sizes between 2 and 20 components and deployed them into multiple OSGi frameworks (Apache Felix1 2.0.1, Eclipse Equinox2 3.5.1, Knopflerfish3 3.0.0) and checked that they correctly installed. To further validate our results we used another implementation of a DR algorithm, Eclipse P2 [2] (DRp2 ), the current provisioning system for the Eclipse IDE platform. We ensured that the search space of our DR∗ (7), contained the solutions returned by Eclipse P2 (DRp2 (κ) ∈ DR∗ (κ)), when queried on the datasets. These tests approximate our DR implementations validity by showing that we are unable to find a query where a returned solution is incorrect.
5
Datasets
The first set we collected for use is from the Spring Enterprise Bundle Repository (SEBR)4 which is made from a set of libraries commonly used for enterprise software that have been repackaged into OSGi bundles. SEBR is a reuse oriented repository, created so many applications can reuse its contained components. SEBR can be used with Maven [1] and its DR algorithm for project management and build automation. The Eclipse Project5 is aimed at a universal tool-set for development, this is an application centric dataset. The two core locations for Eclipse 3.5 components6 were aggregated together to create this dataset. This dataset is primarily used by Eclipse P2 resolver for updating and extending Eclipse based applications. 5.1
Restrictions
When using these datasets some OSGi implementation details that are not within our model must be ignored or altered. These restrictions will reduce the search space, and therefore they cannot create invalid solutions within the DR∗ set. This means that these restrictions can not introduce false positives, but possibly some false negatives. We ignore all requirements that are optional (including dynamic imports [12]), we also ignore the OSGi uses directive. We treat requirements on a package provided by the OSGi framework or the Java 1.6 JVM as optional. These requirements are typically provided by the environment the components are executed within, but must not specify a version range, as doing this forces the package to be supplied within the OSGi framework. 1 2 3 4 5 6
http://felix.apache.org http://www.eclipse.org/equinox/ http://www.knopflerfish.org/ http://www.springsource.com/repository/app/ accessed 11/8/2009. http://eclipse.org accessed 21/8/2009. http://download.eclipse.org/eclipse/updates/3.5; http://download.eclipse.org/releases/galileo
190
G. Jenson, J. Dietrich, and H.W. Guesgen
Some bundles are used for content, like source-code or documentation, and not executable code. We identify and remove source-code bundles, however other content bundles can be difficult to identify, such as help system and internationalisation bundles, therefore are not removed. 5.2
Comparison of Sets
These two datasets are fundamentally different, due to their different purposes and producers, and their contents reflect this. Eclipse components are created for a specific application, where SEBR components are created for use by many applications. The structure of these sets is responsible for the search space of DR, therefore analysing them is key. The way we examine these sets is through their DG created through analysis of their contents. This DG can be further subdivided into subgraphs though selecting a single node, and all it connects to. These subgraphs are examined in Table 1 and Figure 3. We also examine the clusters created though the DG dataset, these are subgraphs that are not weakly connected. In Table 1 we see that these data sets are both dominated by a single large cluster, with many singleton components. Singleton clusters, components that neither depend on nor are depended on by any other component, are more abundant in the Eclipse dataset than SEBR. This is because Eclipse can use components to only supply non-code resources like help pages, which we tried to minimise as discussed in section 5.1. The diameter of the problem, the longest shortest path between any two nodes, describes the amount of layers that must be resolved to find a solution. However looking at Figure 3 we see the distribution of the diameters also to be drastically different. Eclipse’s distribution has a greater range with a lower median, where SEBR has more than half of its bundles in its DG with its maximum diameter of six. The size of the subgraphs, shown in Figure 3, is the measure of the amount of components involved when resolving a query consisting of a single component. Both these datasets have very similar distributions of size, showing that both have around 30 bundles in their average problem. When multiple capabilities satisfy a requirement, DR must select a combination of these to be included in the solution. Each possible capability that satisfies a requirement increases the amount of possible choices combinatorially. Table 1. Metrics of the datasets extracted from their DG Size Mean outdegree in DG Standard Deviation of outdegree Diameter of DG Number of Clusters Number of Singleton Clusters Largest Cluster Size
SEBR Eclipse 1039 1789 7.56 6.13 9.16 6.7 6 12 92 285 84 283 935 1503
An Empirical Study of the Component DR Search Space
191
Fig. 3. Graphs comparing the datasets of Spring Enterprise Bundle Repository (SEBR) and Eclipse Update Sites
192
G. Jenson, J. Dietrich, and H.W. Guesgen
A good example exists in the SEBR set where the requirement on the package org.osgi.framework has 22 satisfactory capabilities, this leads to a search space size of over 4,000,000. Figure 3 shows that the providers per requirement in the Eclipse dataset are mostly 1, with a few higher, this means that there is little to no choice in the Eclipse dataset. However SEBR has a very large average and range of this metric, therefore when resolving SEBR many choices must be made. This is because SEBR has multiple versions of individual bundles, and multiple vendors for standardised packages (for instance the org.osgi.service.log is supplied by two vendors), where Eclipse is only composed of up-to-date versions from one vendor. Eclipse has fewer providers per requirement and a lower median diameter; making its structure less complex to resolve. On the other hand, SEBR has many large diameter subgraphs and large amounts of providers per requirement, making this structure difficult to resolve.
6
Empirical Investigation
Using the implementation described above we can now measure the search space of DR on our datasets. The size of the search space can be so large that measuring it can be impractical. This problem was identified when resolving the SEBR set. To reduce this problem we then limited the measured search space size to 2,000,000 feasible solutions when resolving the SEBR set, that is to say that once we discovered the two millionth solution we stopped counting and tagged that query as still unresolved. This limit was found by continually increasing the amount of solutions we measured till a point was reached where the time estimated for the next size iteration was impractically large. No limit was needed for the Eclipse dataset as this problem was not encountered. The effect that this limit has on the findings from the SEBR set are discussed when presenting our results. 6.1
Initial Results
The main intent of these experiments is to measure the size of the DR search space, in order to estimate feasibility of additional optimisation. The total search space for DR is measured by taking all singleton queries (∀κ ∈ 2C : |κ| = 1) and measuring the cardinality of the search space for each (|DR∗ (κ)|). Shown in Table 2 are the metrics from this measurement. Although SEBR looks to have better metrics it contains less than half the set (510/1039), as the rest exceeds the limit of more than two million solutions. This is obvious when observing the median which is stated as being 1, where in actuality it is greater than 2,000,000 as the majority of the measured search space sizes are above that mark. Also, the maximum amount of solutions (6,111) is very small when compared to the 2,000,000 limit. This shows that there is a significant difference in amount of solutions, between the included and excluded queries.
An Empirical Study of the Component DR Search Space
193
Table 2. Metrics of the size of the search space for DR SEBR Eclipse Size 510/1039 1789 36.36 12,966 Mean 327.62 170,501.12 Standard Deviation Median 1 12 6,111 2,286,360.0 Maximum No Solutions 36 1
The Eclipse dataset varies greatly, where most of the queries have a search space smaller than 12 but a significant amount still have very large search spaces. The main point this data shows is that there are many queries that have excessive amounts of solutions, in both SEBR and Eclipse datasets. 6.2
Additional Constraints
The DR function can be further restricted with additional constraints derived from desirable solution qualities. The three additional constraints experimented with are that solutions should be acyclic, there should be only one provider for a given requirement installed, and a solution should not be a superset of another solution offered. Directed Acyclic Graph Constraint. Cycles in a dependency graph are named circular dependencies, these can lead to complicated or impossible installation and are considered an anti-pattern. The circular dependencies are removed by cutting edges farthest from the query in the DG that cause cycles. Although this constraint is preferable, it can over restrict the search space by removing all possible solutions. However no solution may be better than a cyclic one, as circular dependencies could be an overall detriment to a system. One Capability per Requirement. If a component system has a requirement that is satisfied by two or more capabilities, a decision must be made to select the capability to satisfy the requirement. For example, if a component requires a logging package and two are provided, then one must selected to be used. This complicates the runtime behaviour of the system, as seen in the OSGi specification’s [12] complex definition of wiring. This complexity is sometimes necessary for a component model to have, however it adds overhead, and a system without this complexity is probably a better choice than one with it. This additional constraint is implemented by replacing the OR operator with XOR operator in all constraints created by the completeness constraint (3). From our previous example, a requirement on a logging package would state that it can be satisfied by exactly one possible capability, this would constrain the solution to only containing one logging package. As in our above example, the additional constraints would be; c1 depends on either c2 or c3 (i.e. c2 and c3 cannot both be in the solution), or {c1 → (c2 ⊗ c3 )}, leading to the solutions
194
G. Jenson, J. Dietrich, and H.W. Guesgen
{c1 , c2 } and {c1 , c3 , c4 }. The XOR constraints are then mapped to SAT using standard logical mechanisms. Like removing cycles this is a constraint describing an ideal, yet possibly impractical solution as it may remove all solutions. For example if components a, b, c, d had dependencies such that a → b, b → c, c → d and a → c ∨ d. The only solution is to include all of the above components, however with this constraint a → c ⊗ d, no solution exists. Minimal Model Constraint. The minimal model constraint states that no solution in the search space is a superset of another solution, i.e. if A, B ∈ DR∗ (κ) then A B. This is implemented by having the SAT resolver return minimal sets first (by using an appropriate search strategy), then adding the inversion of the solution as a constraint. In the example above when the solution {c1 , c2 } is found the constraint ¬c1 ∨ ¬c2 is added to the SAT resolver. The solution {c1 , c2 , c3 , c4 } is then not within the constraints and is not a solution. This constraint, unlike the previous two, will always return a solution if a solution exists. 6.3
Additional Constraint Results
After the addition of these constraints we measure the resolution solutions as we did in section 6. The one additional metric measured is the amount of over restricted solutions it has. This is the amount of queries where the additional constraints removed all possible solutions. Directed Acyclic Graph Constraint. The results shown for this constraint in Table 3 are not very successful. This constraint over restricts and removes many valid solutions. One Capability per Requirement. As shown in Table 3 this constraint is partially successful, as in SEBR it greatly reduced the search space size with minimal over restrictions. However for Eclipse, it drastically over restricts the search space, removing many possible solutions. A probable cause for this are standards like importing a package that you also export. Table 3. Metrics of the size of the solution sets of resolution with Directed Acyclic Graph Constraint (DAG), One Capability per Requirement (OCpR) and Minimal Model Constraints DAG Constraint SEBR Eclipse Size 955/1039 1789 17,233.63 92.75 Mean Standard Deviation 121,975.33 1,204.78 1 1 Median 1,584,219 39,366 Maximum Over Restricted 112 537
OCpR Constraint Minimal Constraint SEBR Eclipse SEBR Eclipse 1008/1039 1789 1030/1039 1789 41,878.56 2.99 18,216.98 4.58 189,935.44 18.89 128,115.72 19.23 4 1 16 2 1,988,520 512 1,964,312 512 7 615 0 0
An Empirical Study of the Component DR Search Space
195
Fig. 4. Comparison of the dataset search spaces with constraints
Minimal Model Constraint. The minimal model constraint is shown in Table 3 to successfully reduce the size of the resolved sets, and due to its properties cannot over restrict a solution. It still does not reduce all queries in SEBR below the limit, showing how enormous some solutions sets are. 6.4
Analysis
Although the additional constraints do significantly reduce the search space of DR, none of them completely reduce the size of the SEBR set to under the two million solutions limit. Within Eclipse there is a much greater reduction, from a maximum of 2,286,360 to 512 solutions, a median of 12 to 2. This is then
196
G. Jenson, J. Dietrich, and H.W. Guesgen
a partial success, where these constraints work for many, but not all queries when resolving. A comparison of the additional constraints search space distributions (on logarithmic scales) can be seen in Figure 4. Here we can see both datasets and the relative success of the constraints. The Eclipse set clearly benefits from the minimal constraint, as above 90% of the solutions are between 1 and 10, with no over restricted solutions. Each of the other two constraints over restricts nearly a third of their queries, making them unattractive. Figure 4 clearly presents a trade off for the SEBR set, where the removal of cycles drastically lowers the search space, yet also over restricts resolution. The minimal model and one capability per requirement constraints are similar in distribution, though minimal model has lower mean and higher median. We also tested all combinations of these constraints, but do not present those results here as they do not greatly affect the results.
7
Future Extensions
The main goal of this line of research is to study and deliver a full DR algorithm with optimisation. This goal will require us to: define, represent and implement the optimisation function σ; research into the invariants of DR between different component models; and to further develop the component model the DR algorithm acts on. Most optimisation algorithms would optimise while searching, instead we split our algorithm into the generation of all possible solutions and the selection of an optimal solution, as this allowed us to measure, and restrict, DR’s search space. As other algorithms operate within this search space, its dimensions can be used to determine their feasibility, and its restriction can increase their performance. However our current method is not suitable for production, therefore to improve our DR algorithm we plan to optimise while searching. Currently our DR algorithm is incomplete, as optimisation (σ) is yet to be defined. The two main topics related to this optimisation function, is its representation and its implementation. How this optimisation function should be represented should be communicable and complete while retaining implementation characteristics of being scalable and practical. Using Branch and Bound (or similar methods) to implement optimisation will be explored. The gathering and use of information relevant to the dependency decision is a future area of research as well. This information could be gained through performance prediction frameworks like Palladio [14], or component contract verification frameworks like Treaty [15]. One great limitation of this study is that it is using a specific component model, OSGi. To have our DR algorithm usable with more component models, each model would also have to be mapped to our component model, as OSGi was. To fully realise this, additions to our base component model and DR algorithm may be required, as other component models can have more complex requirements, can allow component conflicts, or have other specific complexities not currently able to be represented by our component model. Also as
An Empirical Study of the Component DR Search Space
197
these component models differ, it can also effect DR through the granularity of components, the way dependencies are handled, and other variables unique to each component model, meaning DR may have to be extended. However using a single DR algorithm over multiple component models has obvious benefits, therefore the extension of our model and algorithm in this direction is a topic for future research. Components interactions are more complex than that described in our model. Components can have more types of requirements like component conflicts or requirements on execution environment, and requirements can have properties like multiplicities or being optional. These would be practical additions to our component model and as they are currently used supporting them is a necessity, however their representation and execution also increase the complexity of DR.
8
Related Work
The Mancoosi project is a European Commission project to model, and implement safe upgrades of component based systems. Its targeted environment is the dependency relationships in Free and Open Source Software (FOSS), Mancoosi’s main goals differ from ours in two ways, its central focus is on a different component model, Linux packages, and it is focused on safe upgrades rather than DR. This project still provides insight into many related problems of DR, like the modelling of component relationships [16]. A related project to Mancoosi is the Environment for the development and Distribution of Open Source software (EDOS) project [17]. This project finished in 2007 with proposed improvements for dependency resolution, empirical results based on Debian packages and formal models for dependency resolution. Le Berre and Parrain [4] describe methods for SAT solver integration into dependency resolution. This study is a more focused extension of their research in the definition and use of SAT as a method of dependency resolution. The Software Product Lines area is also closely related to our research topic. Savolainen et al. [3] uses dependency analysis to identify and remove unnecessary features, where Post and Sinz [18] use a SAT based model checker to check configurable software feature models for consistency. Feature dependencies are not necessarily the same structure or complexity as dependencies between components, therefore their search space’s may differ significantly making our study inapplicable, however common problems exist to be shared and solved. Myll¨ arniemi et al. [19] creates a DR algorithm to find compositions that satisfy functional and security requirements. This study demonstrates the applicability of their approach but with no performance measuring or feasibility study of the potentially complex large problems in the domain. This research is useful as it demonstrates the addition of non-functional requirements to DR, yet their focus is not on optimisation but satisfaction. The automotive and embedded component research field also has much relevant research, including the intensive use of SAT technologies by Sinz et al. [20] to validate configuration data and algorithms for multiobjective optimisation of component configurations by Aleti et al. [21] .
198
9
G. Jenson, J. Dietrich, and H.W. Guesgen
Conclusion
Through this paper we have developed a working component model, and dependency resolution function with an implementation for OSGi bundles. We then experimented on two datasets and found the size of the search space for DR to be massive, demonstrating the infeasibility of many optimisation techniques. Yet after the addition of constraints based on desirable properties the space can be significantly reduced to make optimisation possible. The most successful constraint is the minimal model constraint, which states that no solution can be a superset of another. This approach however did not work for all queries as some retained massive search spaces, therefore further investigation will have to be made into the optimisation of DR.
References 1. Casey, J., Massol, V., Porter, B., Sanchez, C.: Better Builds with Maven (2008) 2. Berre, D.L., Rapicault, P.: Dependency Management for the Eclipse Ecosystem. In: IWOCE 2009 (2009) 3. Savolainen, J., Oliver, I., Myllarniemi, V., Mannisto, T.: Analyzing and Restructuring Product Line Dependencies. In: Computer Software and Applications Conference, vol. 1, pp. 569–574. IEEE Computer Society Press, Los Alamitos (2007) 4. Berre, D.L., Parrain, A.: On SAT Technologies for dependency management and beyond. ASPL (2008) 5. Szyperski, C.: Component Software: Beyond Object-Oriented Programming, 2nd edn. The Component Software Series. Addison-Wesley Longman Publishing Co., Inc., Boston (2002) 6. David, P.C., Ledoux, T.: Towards a framework for self-adaptive component-based applications. Distributed Applications and Interoperable Systems, 1–14 (2003) 7. Plasil, F., Balek, D., Janecek, R.: SOFA/DCUP: architecture for component trading and dynamic updating. Configurable Distributed Systems, 43–51 (1998) 8. Berre, D.L., Parrain, A.: SAT4J at the SAT09 competitive events. In: Kullmann, O. (ed.) SAT 2009. LNCS, vol. 5584, Springer, Heidelberg (2009) 9. Ouimet, M., Lundqvist, K.: The TASM toolset: Specification, simulation, and formal verification of Real-Time systems. In: Damm, W., Hermanns, H. (eds.) CAV 2007. LNCS, vol. 4590, pp. 126–130. Springer, Heidelberg (2007) 10. Batory, D.: Feature models, grammars, and propositional formulas. Software Product Lines, 7–10 (2005) 11. Schaefer, T.J.: The complexity of satisfiability problems. In: STOC 1978: Proceedings of the tenth annual ACM symposium on Theory of computing, pp. 216–226. ACM, New York (1978) 12. The OSGi Alliance: OSGi Service Platform Core Specification (2007) 13. Kriens, P.: How OSGi changed my life. Queue 6(1), 44–51 (2008) 14. Becker, S., Koziolek, H., Reussner, R.: Model-Based performance prediction with the palladio component model. In: Proceedings of the 6th International Workshop on Software and performance, pp. 54–65. ACM Press, New York (2007) 15. Dietrich, J., Jenson, G.: Treaty - a modular component contract language. In: WCOP 2008 (2008)
An Empirical Study of the Component DR Search Space
199
16. Pierantonio, A., Zacchiroli, S., Ruscio, D.D., Pelliccione, P.: Metamodel for describing system structure and state (2009) 17. Abiteboul, S., Dar, I., Pop, R., Vasile, G., Vodislav, D., Preda, N.: Large scale P2P distribution of open-source software. In: VLDB 2007, VLDB Endowment Vienna, Austria, pp. 1390–1393. (2007) 18. Post, H., Sinz, C.: Configuration lifting: Verification meets software configuration. 23rd IEEE/ACM International Conference on Automated Software Engineering ASE 2008, 347–350 (2008) 19. Myllarniemi, V., Raatikainen, M., Mannisto, T.: Using a configurator for predictable component composition. In: 33rd EUROMICRO Conference on Software Engineering and Advanced Applications, pp. 47–58 (2007) 20. Sinz, C., Kaiser, A., K¨ uchlin, W.: Formal methods for the validation of automotive product configuration data. Artif. Intell. Eng. Des. Anal. Manuf. 17(1), 75–97 (2003) 21. Aleti, A., Grunske, L., Meedeniya, I., Moser, I.: Let the ants deploy your software: an ACO based deployment optimisation strategy. In: ASE (2009)
Component Composition Using Feature Models Michael Eichberg1, Karl Klose2 , Ralf Mitschke1 , and Mira Mezini1 1 Technische Universit¨ at Darmstadt, Germany {eichberg,mitschke,mezini}@st.informatik.tu-darmstadt.de 2 Aarhus University, Denmark [email protected]
Abstract. In general, components provide and require services and two components are bound if the first component provides a service required by the second component. However, certain variability in services – w.r.t. how and which functionality is provided or required – cannot be described using standard interface description languages. If this variability is relevant when selecting a matching component then human interaction is required to decide which components can be bound. We propose to use feature models for making this variability explicit and (re-)enabling automatic component binding. In our approach, feature models are one part of service specifications. This enables to declaratively specify which service variant is provided by a component. By referring to a service’s variation points, a component that requires a specific service can list the requirements on the desired variant. Using these specifications, a component environment can then determine if a binding of the components exists that satisfies all requirements. The prototypical environment Columbus demonstrates the feasibility of the approach.
1
Introduction
Components in a component-based system may provide and require multiple services, whereby each service is described by a service specification. A component that provides a specific service declares to do so by implementing the interface defined by the service specification. This approach of “programming against interfaces” enables low coupling and flexible designs that are malleable. Current interface description languages (Java interfaces, WSDL interfaces, etc.) are geared towards describing commonalities between components and hiding their variabilities. However, in an open component environment, several components may co-exist that do implement the same programmatic interface, but with varying characteristics of their implementations regarding functional as well as non-functional properties. For example, it is possible that two components implementing two Payment Web Services expose exactly the same programmatic interface, but do support a different set of credit card vendors, use different security algorithms and have different levels of reliability. The description of the interface using, e.g., the Web Service Description Language (WSDL), only specifies how to interact with a web service; i.e., the data types that have to be used, the order in which the messages have to be exchanged, the transport protocol L. Grunske, R. Reussner, and F. Plasil (Eds.): CBSE 2010, LNCS 6092, pp. 200–215, 2010. c Springer-Verlag Berlin Heidelberg 2010
Component Composition Using Feature Models
201
that has to be used, and where the service resides. However, a WSDL file cannot be used to specify a service’s variability; such as, a services’ non-functional properties (e.g. reliability) or variability in the semantics of a service (supported credit card vendors) as outlined above. Such varying characteristics may be important to clients and need to be taken into consideration when binding a component that provides a service to a client that requires it. Given current interface and binding technology such situations require that the component binding is manually configured. This observation has motivated our work on a new technique for component interfaces and bindings based on them. As with traditional component based approaches, the selection and the binding of components is done by a component environment. However, unlike traditional approaches, the selection and composition is based on extended interface specifications of services that – in addition to specifying the provided functions – also enable a well-structured description of the service’s variability space. To describe this variability, we propose to use feature models (FMs) from Feature Oriented Design Analysis (FODA) [1,2,3]. More specifically, our work is based on the formal underpinnings developed in [4]. A feature is a prominent or distinctive and user-visible aspect, quality, or distinctive characteristic of a software system or systems. FMs were originally developed to describe both the commonalities and variabilities of systems in a specific domain. Using feature models the variability space is described by defining which feature combinations are valid. However, we use feature modeling only for describing the variability of services. We continue to use standard interfaces, such as, Java or WSDL interfaces, to describe the commonalities of services. In our approach, a service is specified by a programmatic interface (definition of the functionality) and a feature model modeling the variability space of possible implementations of the programmatic interface. Both, components that implement a service and clients that require it, specify the provided, respectively required features, in terms of the service’s feature model. The runtime environment is responsible for matching a client’s requirements including those regarding the features of the implementation against components that implement the service. As a result, a component that provides the required variant of a service is bound to the client. To evaluate the feasibility of the approach, we have implemented a runtime environment called Columbus1 that supports the automatic binding of components where variability in the component’s required and provided services is described using feature modeling. This binding is performed at runtime. The advantages of our approach are the following. First and foremost, it enables decoupled composition of clients and services in cases where clients have specific requirements on services that cannot be specified using programmatic interfaces. Further, it supports the verification of the consistency of provision and requirement specifications against well-defined formal models of the variability space. Last but not least, the approach enables the runtime environment 1
www.st.informatik.tu-darmstadt.de/Columbus
202
M. Eichberg et al.
to optimize the system configuration. If more than one feasible binding exists, it is possible to choose the optimal binding/configuration regarding a specific criteria, such as, the overall number of instantiated components. The remainder of the paper is organized as follows. In Sec. 2 we present an overview of the proposed approach and discuss the specification of services and components w.r.t. feature models and how the environment determines the binding. Along with the discussion a motivating example is presented. The implementation of Columbus is presented in Sec. 3. Sec. 4 discusses the proposed approach. This paper ends with a related work section (Sec. 5) and a summary (Sec. 6).
2
High-Level Overview of the Approach
In the following, we give a high-level overview of the approach. The relation between service specifications, service implementations (components), clients, and the runtime environment – the (component) container – is presented. Central to our approach are service specifications (cf. upper part of Fig. 1), which serve two purposes: (1) to define how a client can interact with a component, and (2) to describe the possible variants of the service. To specify the interaction aspect we rely on standard interface descriptions, such as Java interfaces, or WSDL [5] resp. WADL [6] files. A service’s variabilities are specified explicitly in a Feature Model (FM) [2,1].
Service Specification uses the interface to interact with a concrete service
a satisfiable instance of the feature model
Service Interface (Specifies the programmatic interface; basically, the service's commonalities.)
implements the interface
Feature Model (FM) (Specifies the service's variabilities.)
instance of the feature model that satisfies all constraints
Client Feature Requirements Specification (FRS) (Specifies the features that an implementation of the service specification must have and the features an implementation does not have to have.)
Service Feature Model Instance (FMI) (Specifies all provided features of a service.)
Fig. 1. Overview of the central artifacts of the approach
Component Composition Using Feature Models
203
Payment WS [0..1]
[0..1]
[1..1] Guaranteed Availability [Percentage]
Security [1..16]
[1..1] Protection Order 2
Basic128
...
[1..1]
EncryptBeforeSign
[1..1]
Identity Federation 1
Basic256Sha256RSA15
Authentication
requires SAML
[1..4]
3
Password
X.509 SignBeforeEncrypt Kerberos
Fig. 2. Feature model covering variability in implementing security of Payment Services
Our FMs, support the basic notion of cardinality-based feature groups, constraints between features, and attributed features [2]. Attributed features are necessary when modeling non-functional requirements, such as, costs, reliability, execution time and memory requirements. 2.1
A Service’s Variability Space as a Feature Model
For illustration, we discuss the implementation of a simple Payment Web Service. We assume that an interface is defined that enables a client to make payments. In the following, we focus on the specification of the variability of the Payment Service. The services’ feature model is shown in Fig. 2. For brevity, we focus on modeling the variability regarding security aspects of (payment) web services. Other kinds of variability of Payment Web Services w.r.t. functional or nonfunctional requirements, e.g., costs per transaction, response time, supporting a different set of credit card vendors, etc., can be modeled in a similar way, but are left out for keeping the presentation focused. A number of specifications exists for web service security [7,8,9] that can be used to derive a feature model that captures the essential variability of web service w.r.t. security. The Authentication and the Encryption features are modeled roughly following the WS-SecurityPolicy standard. This standard lists sixteen algorithm suites (Basic128 ... Basic256Sha256RSA15) to describe how a message is encrypted. We have modeled this variability using the feature group that is the child of the Security feature (Group 1 in Fig. 2). The group’s cardinality is [1..16] which necessitates that each Payment Web Service supports at least one of the encryption algorithms. A service can also declare to support any number of the sixteen algorithms. Further, the standard describes different Protection Orders that are possible: a message is either first encrypted and then signed or the other way round. We represent this variability using a feature group with the cardinality [1..1] – also called an exclusive or feature group – (Group 2 in Fig. 2). The variability in the supported authentication mechanisms is again modeled using a feature group (Group 3 in Fig. 2). A service can declare that it supports
204
M. Eichberg et al.
between one and all four authentication mechanisms. If a service supports Identity Federation then it has to support SAML tokens for authentication. Finally, we have added an attributed feature to enable a service to optionally specify its Guaranteed Availability. 2.2
Characterizing Services Using Feature Model Instances
A service, or service implementation, is one possible implementation of a service specification. As usual, a service has to fulfill the contract laid out by the service specification. Each service consists of the implementation of the service specification’s interface and a description of the implemented variant. The model which specifies the variant provided by a service is called a Feature Model Instance (FMI). A FMI is a selection of features that is valid under the constraints defined by the FM that is part of a service’s specification. The FMI is complete in the sense that it specifies for every feature identified by the FM whether or not it is provided. Thus, every FMI describes one possible variant of a service and for each service provided by a component the implemented FMI is specified. For illustration, Fig. 3 and 4 graphically show the FMIs of two different implementations of the Payment Web Service, a basic and a more advanced implementation. Both instances of the feature model satisfy all constraints of the Payment Web Service’s feature model. Payment WS
Guaranteed Availability [Percentage]
Protection Order
Security
Basic128
...
Identity Federation
Basic256Sha256RSA15
Authentication
SAML
X.509 EncryptBeforeSign
Password
SignBeforeEncrypt Kerberos
Fig. 3. Features provided by the basic Payment Web Service
The feature model instance of a basic Payment Web Service is shown in Fig. 3. This service supports authentication using X.509 certificates and passwords. Furthermore, all messages are first signed and then encrypted using the Basic128 algorithm suite. This service does not specify its (guaranteed) availability. The feature model of a more elaborated version of a Payment Web Service is shown in Fig. 4. This service supports Identity Federation as well as SAML tokens, X.509 certificates and Kerberos for authentication. Additionally, the service uses a more secure encryption algorithm and specifies that its Guaranteed Availability is larger than 99%.
Component Composition Using Feature Models
205
Payment WS
Guaranteed Availability [99%]
Protection Order
Security
Basic128
...
Identity Federation
Basic256Sha256RSA15
Authentication
SAML
X.509 EncryptBeforeSign
Password
SignBeforeEncrypt Kerberos
Fig. 4. Features provided by the advanced Payment Web Service
2.3
Characterizing a Client’s Needs in a Feature Requirements Specification
A client specifies its requirements on specific characteristics of the implementation of a service using a Feature Requirements Specification (FRS). A FRS does not have to be complete: An FRS is correct if potentially at least one service variant can realize the required features. In Fig. 5 a client’s requirements on a Payment Web Wervice are shown. In general, a client just lists those features that a service has to support and those features that a service does not have to provide. If a feature is in neither of the groups, we say that the client is agnostic of the feature; i.e., the client does not care whether the feature is available or not. In the given example, the client specifies that it requires the service’s reliability to be larger than 95%. Additionally, the client specifies that the payment service has to support advanced message encryption. Since the client does not require a specific protection order, the client has to be able to handle both types of protection orders. With respect to authentication the client only requires that the service supports identity federation. Given the constraints defined on the feature model, each service that offers identity federation also has to support Payment WS
Guaranteed Availability [>95%]
Protection Order
Security
Basic128
...
Identity Federation
Basic256Sha256RSA15
Authentication
SAML
X.509 EncryptBeforeSign
Password
SignBeforeEncrypt Kerberos
Fig. 5. A client’s feature requirements on a Payment Web Service
206
M. Eichberg et al.
SAML based authentication, but the client does not have to explicitly specify this dependency since a client only has to specify those features that are necessary to distinguish compatible and incompatible services. In the given example the client also specifies that compatible Payment Web Services do not have to offer password based encryption, e.g., because these services are deemed insecure. 2.4
Client-to-Component Binding
Given (i) a service specification, (ii) the services’ variant definitions (FMI), and (iii) the clients’ specified requirements (FRS) – the runtime environment first checks whether the service’s variant definition is valid and whether the client’s requirements are potentially satisfiable. For the first check the environments tests whether the specified variant is an element of the set of all variants described by the service’s feature model. For the second check, the environment tests whether at least one of the variants described by the feature model of the service satisfies the client’s requirements; i.e., whether or not there is a variant that contains resp. does not contain the features as required by the client. After that, the environment matches the client requirements against the definition of the provided variants to identify those services the client can be bound with. Eventually, the runtime environment binds the components. The requirement on the service reliability to be higher than 95% in the FRM in Fig. 5, e.g., rules out the basic payment service since it does not specify its availability.
3
Implementation
We have implemented a Java-based runtime environment – named Columbus – that realizes the concepts presented so far. Columbus uses the Prolog engine tuProlog [10] for feature model encoding and processing. Columbus currently only supports standard Java interfaces as the interface description language (IDL) for services.2 At runtime, components are first registered with Columbus. After that it is possible to ask Columbus to instantiate a registered component. Columbus will then try to resolve all declared dependencies and – if possible – instantiate the component. The components are bound using dependency injection [11]. In the following, we describe how feature models are represented and processed and how the results are used to find optimal configurations which satisfy all client requirements. Feature models are represented as Prolog terms in our approach. The main building blocks are features and feature groups. Features are represented as Prolog terms with the feature name used as functor, and two arguments: the first argument is a list of attributes of the feature – together with constraints on the possible values of the attribute – and the second argument is a list of feature 2
Supporting other IDLs is not related to conceptual issues and is planned for future work.
Component Composition Using Feature Models
207
groups under this feature. Listing 1 shows the representation of the features Security and Guaranteed Availability from the Payment Web Service example discussed earlier. If a feature has no attributes, the argument can be omitted (as with Security) and if there are no sub feature groups under the feature, it can be represented as an atom, for example the feature EncryptBeforeSign. Features that belong to a feature group are specified using a pair of a list of constraints and a list of features. The feature group in Lines 3 and 4 in Listing 1 restricts to selecting exactly one of the two features in a valid instance using the constraint oneof. 1 2 3 4 5 6 7
’Security’([ ([oneof], [’ProtectionOrder’([ ([oneof], [’EncryptBeforeSign’, ’SignBeforeEncrypt’])])]), ([atleastone], [’Basic128’, ’Basic256Sha256RSA15’])])), ... ’Guaranteed Availability’([’Percentage’([int_between(1,100)])], [])), Listing 1. Example FM and FMI
A feature model instance (FMI) is a term using the same functor as the feature model and which has a (possibly empty) list of selected features for each feature group. For an FMI to be valid, the constraints of all its feature groups must be satisfied: first, all the features selected in a feature group must in fact be part of this feature group in the feature model. Second, all constraints of the feature group must be satisfied with respect to the list of selected features in the feature model instance. Finally, if there are constraints on the values of feature attributes, the constraint must be satisfied for the given instance value. In contrast to other feature models, we allow arbitrary constraints to be used and users can provide their own constraints in the form of Prolog predicates. When a constraint is about to be checked, the corresponding predicate is called via reflection; thereby the subtree of the feature model instance corresponding to the feature group is passed as an argument. The following code shows the implementation of basic number constraints: 1 2 3
oneof(L) :- length(L,1). atmostone(L) :- length(L,N), N < 2. atleastone(L) :- length(L, N), N > 0.
Columbus also supports parameterized constraints. For example, a constraint nToM, which checks whether there are at least N and at most M selected features for a group. It is implemented as follows: 1
nToM(N,M,L) :- length(L,K), N < K+1, K < M+1.
The constraint can be used in a feature model with concrete values for N and M , e.g., nToM(2,5); the FMI checker constructs a call to the predicate nToM(N,M,L) where the third parameter (L) is the list of features declared in the feature model instance.
208
M. Eichberg et al.
Finally, Columbus supports constraints between features in different feature groups (cross feature constraints), like requires and conflicts. For example, a requires constraint, like requires(A, B) will search for occurrences of A and B in the complete subtree of the feature model selected in the instance; the constraint is satisfied, if either both features are present or the first feature is absent. To support such features, the root of the feature model is modeled not as a feature but as a feature group, in which the cross feature constraints are specified, if there is no other feature group that is a parent of both referenced features. The following code shows the root of the Payment Web Service feature model, which specifies the requires constraint on Identity Federation and SAML: 1 2
([requires(’Identify Federation’, ’SAML’)], [’Payment WS’( . . . )]).
The attribute values of attributed features, e.g., the percentage of the guaranteed availability feature, are checked in a similar way. For every attribute, the attribute constraints are translated to Prolog predicate calls. In contrast to feature group constraints, the predicate is not called with the instance subtree as an additional argument; instead, the selected attribute value is used. To check the constraint ’Percentage’([int between(1,100)]) for an attribute value ’Percentage’(95), for example, the system calls int between(1,100,95) using reflection. To find a service that matches the client’s requirement, first all compatible services are searched for by checking if their feature model instances satisfy the feature requirements model of the client. After that, one of the compatible services is instantiated and bound. If one or more of the compatible services are already instantiated an existing instance is reused.
4
Discussion
In this section, we first discuss the performance implications of the proposed approach. After that, we discuss its general applicability and also discuss the use of feature models for evolving software systems. 4.1
Performance
The time required to dynamically check a feature model is low: For feature models of the size of the example, finding all valid instances takes about 50 ms.3 Testing if a particular feature model instance is in fact the instance of a particular model or checking a feature requirements model for satisfiability is much faster. Hence, using Columbus in settings where the configuration; i.e., the deployed services, changes frequently at runtime is possible. Most of the required time is due to the generic implementation of constraints: Without optimization, cross-feature constraints will search their complete subtrees every time. We expect that postponing these checks – until after all instances without cross-feature constraints have been found – would significantly 3
Measured using SWI-Prolog on a 2.6GHz processor.
Component Composition Using Feature Models
209
reduce the runtime for large feature modules with many cross-feature constraints. Implementing such optimizations is left for future work. 4.2
Modeling Service Variability Using Feature Models
Using feature models enables the precise and comprehensible identification of a service’s variability. Using only a service’s programmatic interface it is not possible to distinguish the different service variants. Feature models are the first choice for encoding the variability, because they were invented for modeling variability, are well understood and widely used. Given the formal underpinnings of feature models, it is possible to automate the matching process of service providers and service requesters. As demonstrated by our example, to make feature models useful for modeling the variability of services, support is needed for feature group cardinalities, attributed features and cross-feature constraints [2]. Feature models supporting these modeling formalisms are (at least) well suited for domains where services have a great variability of possible implementations while at the same time a specification of the variability space exists. One example for such a domain are web services complying to the WS* specifications; these specifications define a stable variability space, e.g., the WS security specification precisely enumerates how a message can be encoded. In general, attributed features are particularly required to model variability related to service level agreements. For example, using just features and feature groups it is not practical to model, e.g., a service’s guaranteed availability. 4.3
Evolution of Feature Models
Since change is the only constant in software engineering, it is important to understand the implication of using feature models as part of a service’s interface on the evolvability of components. In Java, e.g., it is well understood how an interface4 can evolve without breaking existing clients (compiled classes) [12, Chapter 13],[13]. Like Java interfaces, a service’s feature model has two clients: (1) the service implementing a specific instance of the feature model and (2) the client of the service that uses the feature model to specify which service providers are compatible. Unlike Java interfaces a service’s feature model can evolve in a number of ways without requiring any components to be updated. For example, consider the original feature model of the Payment Web Service (cf. Fig. 2). To support a new encryption algorithm it is sufficient to add a new feature identifying the algorithm (NewEncryptionAlgorihtm in Scenario A, Fig. 6) to the corresponding feature group and to update the cardinality (17 instead of 16). In this case, no client, i.e., neither existing implementations of the Payment Web Service nor clients of the service need to be updated in any way. Already deployed or even used services and clients can be reused with a new version of 4
Actually, it is well understood how classes can evolve, but here we are only interested in the evolution of interfaces.
210
M. Eichberg et al.
Scenario A
Payment WS
Scenario B
[1..1] Security
[1..1] Encryption
...
...
NewEncryptionAlgorithm
...
[1..16]
[1..17] Basic128
Payment WS
Basic128
...
Basic256Sha256RSA15
Fig. 6. Evolution of the Payment Web Service’s Feature Model
the service specification while new services and clients can make use of the new variability points in the service specification. Hence, in case of the most likely evolution scenario – extension of an existing model – the proposed approach enables a seamless transition of existing services. This evolution scenario is (largely) supported because a FM’s cardinalities are not reflected in the FMIs and FRSs. The changes to a feature model that never require an update of a FMI or a FRS are: raising a feature group’s upper bound, lowering a feature group’s lower bound and adding new optional feature(s) (groups). However, for other evolution scenarios, such as, changing the structure, renaming existing feature (groups) (cf. Scenario B, Fig. 6), raising a group’s lower bound or lowering a group’s upper bound, no definite answer can be given to the question whether and how a client has to be changed. If a variation point is identified after several implementations and is made explicit by adding a new optional feature to the feature model, some (or even all) existing FMIs must be updated to be compatible with the new feature model. 4.4
Optimal Component Bindings
In general, it is possible that multiple clients are simultaneously compatible with different implementations of the same service specification. In this case multiple bindings are possible and the runtime environment has to make a selection. From a functional point of view this is not a problem. Any binding that satisfies the clients’ requirements is considered valid, and thus any valid combination of components may be selected. I.e., it is sufficient to choose an arbitrary valid binding. In a real system, however, certain combinations of components may be preferable to others. It is therefore desirable to determine an optimal component binding for a given set of components. For example, if two components satisfy the availability constraints of a client, it may be desirable to bind the component which guarantees a higher availability. In general the calculation of an optimal binding even depends on the application context; e.g., whether we want to maximize throughput or minimize CPU utilization. Due to this context dependency, the optimal binding cannot be deduced from the service specifications and requirements alone. Instead, it has to be possible to specify an optimization function that takes the current context into
Component Composition Using Feature Models
211
consideration. Such a function may be as simple as minimizing the number of instantiated components, but it may also use information stored in the feature model instances. Currently, Columbus just minimizes the number of instantiated components, but it is possible to add more elaborate optimization functions.
5
Related Work
Robak et al. [14] propose to use feature models to describe the variability of web services. In their work, the authors focus on identifying several sources of variability of web services at the conceptual level, such as the chosen technology and platform. However, neither a concrete example nor issues related to using feature models are discussed; an implementation is also not provided. In [15], the authors discuss the use of feature models for modeling the variability of web services and they also identify web service standards as a source for identifying variabilities. But, they only discuss variability in the implementation of services that manifests itself in a service’s interface, for example, in the data types that are used or in the number of parameters of a method. A similar approach to [15] is also presented in [16]. An overview of techniques for the automated composition of web services, where an adaptation of the control / data flow is necessary, is presented in [17]. Our work is complementary to these approaches since we focus on the variability that cannot be described using standard interfaces or interface description languages and which does not require adaptation of the control / data flow. A language for the specification of Service Level Agreements and, in particular, QoS attributes is proposed by IBM Research [18,19]. The proposed WSLA language supports the definition of arithmetic predicates that constrain the values of attributes (e.g. Availability > 90%). WSLA allows the combination of these predicates using first-order logic expressions. However, it does not offer mechanisms to specify client requirements and to match these against provider obligations. The focus is rather on a framework that continuously measures QoS attributes to provide clients with a platform to compare these values against requirements. Additionally, WSLA does not easily lend itself to specifications of alternative features that are not measured but rather exhibited by a service, as for example the security algorithm suites defined for the Payment Web Service in Fig. 2. By relying on first-order logic expressions for declaring inter-attribute constraints, WSLA lacks the compactness of the feature model notation for specifying hierarchically structured and grouped properties [4]. The Quality of Service Modeling Language (QML)[20] also enables to describe the Quality of Service (QoS) properties of software components. QML was primarily designed to enable the specification of reliability, performance, timing and security properties, but user-defined QoS categories are also supported. QML further enables to dynamically check if one QML specification satisfies another. This enables dynamic component binding. At the conceptual level, a contract type in QML has roughly the same purpose as a feature model in our approach and a contract can be compared to a Feature Model Instance. However, in QML
212
M. Eichberg et al.
QoS properties can only be described using a fixed set of domains and a fixed set of constraints while our approach enables user-defined domains and constraints. The W3C web service standard for expressing service capabilities and requirements – WS-Policy [21] – provides a domain-independent framework with assertions as basic building blocks. An assertion describes a capability that is provided by a service or which is a requirement of a client. Assertions are domain-specific and defined in other WS standards, e.g., by the WS-Security Policy standard [7]). Using WS-Policy, relations between assertions can be specified; e.g., which encryption protocols have to be used / are supported. Given two services with WS Policy specifications it is then possible to determine if these services are compatible, i.e., if the requirements of the services are satisfied by the counter service. Hence, WS-Policy can be used to automatically determine if two services can be bound, but a definition of a service’s variability space is not possible. Furthermore, the kind of relations that can be specified is very limited. The composition of components based on explicitly modeled variability is discussed in [22]. In this approach, feature models, which are called configuration interfaces, are also used to model the variability of a single component. The used notation for describing features models resembles ours, but no support for attributed features and generalized cardinalities exists. Further, the proposed approach is generative: A user has to select the relevant features before the final product can be composed; support for automatic binding of components based on required and provided features is not targeted. Another possibility to describe variability is to associate attributes to components or services. For example, the OSGi framework [23] defines a component model that enables the dynamic discovery and binding of services. OSGi components provide services and register themselves with OSGi’s service registry using standard Java interfaces. While registering, components can define custom properties (key-value pairs) and associate them with the provided services. A client component can query the service registry for an implementation of a service that has specific properties. The query will return all components with a matching service implementation. However, the properties are unstructured key-values pairs which are not checked in any regard. This makes it impossible to decide whether a component provides a feature; it is possible that the component just not defines the property in the way expected by a client. Additionally, in standard OSGi it is the client’s responsibility to query the service registry and to take care of the binding. This issue is addressed in [24], where an extension of OSGi is proposed to automate the service dependency management in environments where services come and go away regularly. But, the decision whether a service is compatible with a client’s requirements is still made solely based on the service’s declared interface. In [25] the authors discuss and classify approaches that support the selection of services based on non-functional properties. They especially derive a set of requirements on approaches that select web services at runtime. All requirements, except one, are largely met by Columbus. We particularly fulfill the requirement that the fully automatic selection of services has to be supported. We currently
Component Composition Using Feature Models
213
do not meet the requirement that user preferences should be taken into account when selecting services. However, support for this requirement could be easily added to Columbus. Use of Prolog for encoding and analyzing feature models is discussed by Beuche [26]. Using Prolog is promising if complex constraints on feature groups and feature dependencies should be supported. In their approach Prolog is used to enable user defined checks and constraints on top of feature models. However, their work targets very large feature models that are used for the generation of products and not for runtime component binding. The formal underpinning and, in particular, the relation between feature models and propositional formulas is discussed by Czarnecki et al. [27] which continues the work of Batory [4]. Batory describes the use of satisfiability solvers (SAT solvers) to check and debug very large feature models - in particular to make sure that a given instance (product configuration) is valid. We currently do not use SAT Solvers; our models are comparatively small and we are primarily interested in matching a client’s requirements against the features provided by a given service. The automatic reasoning on feature models is also discussed by Benavides et al. [28]. They transform feature models into constraint satisfaction problems to answer – among others – the following two questions: which are the products of a model, and how to filter a model. Additionally, they describe the mapping of an extended feature model for modeling extra-functional features on constraint satisfaction problems. Dependency injection [11] is an approach that is used by several component frameworks to automatically bind independent services. In general the binding of two components is either configured explicitly or done based on type information; binding components based on matching these requirements is not supported.
6
Summary and Future Work
This work started with the observation that standard interface description languages are used for specifying the commonalities of services implementing a service specification’s programmatic interface. Variation points in services are not modeled. But, these variation points are often relevant when binding components that require and provide specific service variants. We proposed to use feature models to model the variability space and to make such models an integral part of service specifications. By referring to a service specification’s feature model a component can declaratively specify the service variant it provides and a client can specify its specific requirements on the implementation of a service that it requires. Using feature models as part of service specifications enables the component environment to automatically determine which components can be bound based on matching service variants specifications against the requirements on service implementations. It enables decoupled composition in environments, where clients are not completely agnostic of variations on service implementations. Furthermore, we discussed the requirements on the feature modeling notation that is used for defining variability in services and also presented a prototypical
214
M. Eichberg et al.
component environment that realizes the proposed approach. A motivating example discussing the modeling of the safety and security aspect of services was also presented. In future work we will continue to evaluate the applicability of our approach to other domains. Further, we plan to study the usage of shared feature models; i.e., feature models that are shared among several services. Shared feature models or sub-models would allow the definition of constraints that cannot be expressed with independent feature models. For example, different service specifications can specify cryptographic protocols. Using the current approach, the choice between the protocols in different feature requirement specifications is not related. A shared feature model that describes the cryptographic protocol once and that is referenced by all services requiring cryptography would enable to force all participating services to use the same protocol. Another issue is to investigate how to model non-functional properties of services that depend on the context in which the service is used. Finally, the effects of changes in the feature model part of the interface of a component will be studied in more detail.
References 1. Kang, K., Cohen, S., Hess, J., Novak, W., Peterson, A.: Feature-oriented domain analysis (FODA) feasibility study. Technical report, Software Engineering Institute, Carnegie Mellon University (1990) 2. Czarnecki, K., Helsen, S., Eisenecker, U.: Staged configuration through specialization and multilevel configuration of feature models. Software Process: Improvement and Practice 10(2) (2005) 3. Lee, K., Kang, K.C., Lee, J.: Concepts and guidelines of feature modeling for product line software engineering. In: Proceedings of the 7th International Conference on Software Reuse: Methods, Techniques, and Tools, pp. 62–77. Springer, London (2002) 4. Batory, D.: Feature models, grammars, and propositional formulas. In: Obbink, H., Pohl, K. (eds.) SPLC 2005. LNCS, vol. 3714. Springer, Heidelberg (2005) 5. Christensen, E., Curbera, F., Meredith, G., Weerawarana, S.: Web Services Description Language (WSDL) 1.1. W3C (March 2001), http://www.w3.org/TR/ 2001/NOTE-wsdl-20010315 6. Hadley, M.J.: Web Application Description Language (WADL). Sun Microsystems Inc. (February 2009), https://wadl.dev.java.net/wadl20090202.pdf 7. Nadalin, A., Goodner, M., Gudgin, M., Barbir, A., Granqvist, H.: WSSecurityPolicy 1.3. OASIS (February 2009) 8. Nadalin, A., Goodner, M., Gudgin, M., Barbir, A., Granqvist, H.: WS-Trust 1.4. OASIS (February 2009) 9. Nadalin, A., Goodner, M., Gudgin, M., Barbir, A., Granqvist, H.: WSSecureConversation 1.4. OASIS (February 2009), http://docs.oasis-open. org/ws-sx/ws-secureconversation/v1.4/os/ws-secureconversation-1. 4-spec-os.html 10. aliCE Research Group: Tuprolog, http://www.alice.unibo.it/tuProlog/ 11. Fowler, M.: Inversion of control containers and the dependency injection pattern (January 2004), http://www.martinfowler.com/articles/injection.html
Component Composition Using Feature Models
215
12. Gosling, J., Joy, B., Steele, G., Bracha, G.: Java Language Specification, 3rd edn. Addison-Wesley, Reading (2005) 13. Forman, I.R., Conner, M.H., Danforth, S.H., Raper, L.K.: Release-to-release binary compatibility in som. SIGPLAN Not. 30(10), 426–438 (1995) 14. Robak, S., Franczyk, B.: Modeling web services variability with feature diagrams. In: Chaudhri, A.B., Jeckle, M., Rahm, E., Unland, R. (eds.) NODe-WS 2002. LNCS, vol. 2593. Springer, Heidelberg (2003) 15. Topaloglu, N.Y., Capilla, R.: Modeling the variability of web services from a pattern point of view. In: Zhang, L.-J., Jeckle, M. (eds.) ECOWS 2004. LNCS, vol. 3250, Springer, Heidelberg (2004) 16. Kim, Y., Doh, K.G.: Adaptable webservices modeling using variability analysis. In: Proceedings of the Third International Conference on Convergence and Hybrid Information Technology. IEEE Computer Society, Los Alamitos (2008) 17. Rao, J., Su, X.: A survey of automated web service composition methods. In: Cardoso, J., Sheth, A.P. (eds.) SWSWPC 2004. LNCS, vol. 3387, pp. 43–54. Springer, Heidelberg (2005) 18. Keller, A., Ludwig, H.: The WSLA framework: Specifying and monitoring service level agreements for web services. Journal of Network and Systems Management 11(1), 57–81 (2003) 19. Ludwig, H., Keller, A., Dan, A., King, R.P., Franck, R.: Web Service Level Agreement (WSLA) Language Specification 1.0. IBM Corporation (January 2003), http://www.research.ibm.com/wsla/WSLASpecV1-20030128.pdf 20. Frølund, S., Koistinen, J.: Qml: A lanugage for quality of service specification. Technical report, Software Technology Laboratory, HPL-98-10 (1998) 21. Vedamuthu, A.S., Orchard, D., Hirsch, F., Hondo, M., Yendluri, P., Boubez, T.: Web Services Policy 1.5 - Framework. W3C (September 2007) 22. van der Storm, T.: Variability and Component Composition. In: Bosch, J., Krueger, C. (eds.) ICOIN 2004 and ICSR 2004. LNCS, vol. 3107, pp. 157–166. Springer, Heidelberg (2004) 23. The OSGi Alliance: OSGi Service Platform, Release 4, Version 4.0.1 (2006) 24. Cervantes, H., Hall, R.: Automating service dependency management in a serviceoriented component model. In: Proceedings of the 6th ICSE Workshop on CBSE: Automated Reasoning and Prediction. Carnegie Mellon University/Monash University, USA/Australia (2003) 25. Yu, H.Q., Reiff-Marganiec, S.: Non-functional property based service selection: A survey and classification of approaches. In: 2nd Non Functional Properties and Service Level Agreements in Service Oriented Computing Workshop (2008) 26. Beuche, D.: Composition and Construction of Embedded Software Families. PhD thesis, Otto-von-Guericke-Universit¨ at Magdeburg (2003) 27. Czarnecki, K., Wasowski, A.: Feature diagrams and logics: There and back again. In: Proceedings of the 11th International Software Product Line Conference (SPLC), IEEE Computer Society, Los Alamitos (2007) 28. Benavides, D., Trinidad, P., Ruiz-Cort´es, A.: Automated reasoning on feature mod´ Falc˜ els. In: Pastor, O., ao e Cunha, J. (eds.) CAiSE 2005. LNCS, vol. 3520, pp. 491–503. Springer, Heidelberg (2005)
Restructuring Object-Oriented Applications into Component-Oriented Applications by Using Consistency with Execution Traces Simon Allier1,2 , Houari A. Sahraoui1, Salah Sadou2 , and St´ephane Vaucher1 1 DIRO, Universit´e de Montr´eal, Canada VALORIA, South-Brittany University, Vannes, France {alliersi,sahraouh,vauchers}@iro.umontreal.ca, [email protected] 2
Abstract. Software systems should evolve in order to respond to changing client requirements and their evolving environments. But unfortunately, the evolution of legacy applications generates an exorbitant cost. In this paper, we propose an approach to restructure legacy objectoriented applications into component-based applications. Our approach is based on dynamic dependencies between classes to identify potential components. In this way, the composition is dictated by the context of the application to improve its evolvability. We validate our approach through the study of three legacy Java applications.
1
Introduction
An intrinsic characteristic of software, addressing a real world activity, is the need to evolve in order to satisfy new requirements. Resulting from empirical studies, Lehman’s first law states that software should evolve else it becomes, progressively, less satisfactory [10]. Although old, this law has never been contradicted. The required reactivity (increasingly growing) of software applications, supports for business processes which are evolving more and more quickly, has even increased the scope of this law as the years go by. Maintenance is now, more than ever, an inescapable activity, the cost of which is ever increasing. Estimated at approximately 50 % to 60 % of the software total cost in the eighties and nineties [11,14]; recent studies now evaluate this cost at being between 80 % and 90 % [4,18]. This high cost has undoubtedly been an effective catalyst for the emergence of new programming paradigms. Modular languages, then object-oriented languages, and more recently component-oriented programming, have always had as first justification, the significant increase in maintainability level. These new approaches can be used to build new applications. But what about legacy applications? In this case one can use the techniques of reverse engineering to transform the structure of the application, without changing its functionality, so that it conforms to the new paradigm. In the past we presented a work that allows a company to organize its source code into reusable components [7]. In this work, identifying parts to put together in order to make a component was left in charge of engineers. What we L. Grunske, R. Reussner, and F. Plasil (Eds.): CBSE 2010, LNCS 6092, pp. 216–231, 2010. c Springer-Verlag Berlin Heidelberg 2010
Restructuring Object-Oriented Applications
217
propose is to automatically identify, in the case of object-oriented applications, classes to be grouped to form a component. Some approaches for identifying components [20] or high-level architectures [16,13] already exist. These works generally use the dependencies, between program elements, extracted by static analysis to identify highly cohesive and weakly coupled object-like structures. For components, the idea is to group together classes that contribute to the same functions. Generally, the restructuration of an application aims at improving its maintainability, including its evolvability. And often evolutions have a functional scope. But the classes used for building an application have in most cases a bigger scope than what is needed by the latter. So, unlike the approach based on static dependencies, we promote the dynamic dependencies for the aim of maintainability improvement. Thus, we clam that the most reliable way to determine which class contributes to which function is to execute the program with individual functions. Traces corresponding to execution scenarios could be analyzed to extract functional dependencies. The rest of the paper is organized as follows: Section 2 describes the steps that constitute our approach. Scenarios execution and trace extraction are explained in Section 3. Section 4 gives details of our approach for identifying groups of classes that represent potential components. We provide three case studies in Section 5. Before concluding in Section 7, we describe some related works is in Section 6.
2
Approach Overview
We view a component as a group of classes collaborating to provide a system function. The interfaces provided and required by a component are the method calls respectively from and to classes belonging to other components. So, we propose an approach for identifying components using traces obtained by executing scenarios corresponding to system use cases. The identification of candidate components consists of clustering the classes of the target system in such a way that classes in a group appear frequently together in the execution traces. In the same time, we also try to minimize the coupling between components. Thus, the identification becomes an optimization problem where the goal is to identify groups of classes whose interactions are the most consistent with the execution traces. As this is a NP-hard graph partitioning problem, we use heuristic search to find a near-optimal solution. Therefore, our approach is structured in three steps (see Figure 1): 1. Starting from a set of use case scenarios generate the execution traces. This step allows identifying dependencies between classes; 2. Produce a preliminary set of candidates using a global search. At this stage we use a genetic algorithm to produce this initial solution; 3. Refine the component candidates using a local search. We use a simulated annealing algorithm in order to achieve the local search.
218
S. Allier et al.
Fig. 1. A three-step process for component identification
At the end of the identification process, groups that have a consistency with the traces below a predefined threshold are candidates to be packaged as components. The execution traces are considered as the reference that guides the search for an identification solution. In the first step of the search, a population of potential groupings is created. Following an evolutionary algorithm, new groupings are derived that better match the interactions contained in the execution traces. This step serves primarily to find the region of the search space that has the highest potential to contain the best solution. Another step, in the form of a local search, allows to explore this region to find the near-optimal solution. This approach does not aim to identify reusable components. Indeed, component identification, in our case, is guided by the functional logic of the considered application. Restructuring the application using the identified components has the sole purpose of improving its maintainability. Thus, the goal of our identification process is not to fully re-architect a system into components. Our objective is rather to find groups of classes that could be packaged for reuse purpose. It is true that in the case of a company, who work in a particular application domain, identified components can be considered reusable. But in this case, the execution traces must be obtained from several of its applications. The extraction of the execution traces is described in Section 3. The two steps of the component identification are detailed in Section 4.
3
Execution Trace Generation
For the purpose of component identification, we are interested in finding dynamic relationships between classes. Hence, an execution trace (or call tree) is for us a directed tree T (V, E) where V is a set of nodes and E, a set of edges between
Restructuring Object-Oriented Applications
B.m1
E.m4 E m4
B
A.m2
C.m12
D.m3
H.m6
A.m11
F m5 F.m5
I m7 I.m7
E.m4
J.m8
I.m9
219
B.m13
E
H.m10
A.m11
A
C
D
H
A
F
I
E
J
I
B
H
A
Fig. 2. Example of Execution Trace. (Left) Method-Call Tree. (Right) Corresponding Dynamic Relationships between Classes.
nodes. Each node Vi represents a class of the system (Cli ). An edge Vi , Vj indicates that an object of class Cli calls a method of an object of type Clj . The root of T (V, E) corresponds to the entry point of the system or a thread run by the system. The execution traces are obtained by capturing the calls between instance of classes during an execution of a use case scenario. Every thread, created during the execution, produces an execution trace. In this initial version of the traces, nodes are labeled by both the actual types of the objects that are called and the methods called as showed in Figure 2(left). For example, when execution method m1, object B, instance of class B, called method m2 with as receiver object A, instance of class A. A, in turn, called m3, with as received D of class D. In a second phase, nodes of the execution traces are relabeled by the classes corresponding to the called objects. The relabeling process is straightforward. Indeed as the call tree is generated dynamically, the concrete type of each object is recorded. This second phase produces traces where nodes are classes, and edges, dynamic relationships between classes. In the example illustrated in Figure 2 (right), the sequence is replaced by . The identification of components from execution trace is relevant only if the execution traces cover all the functions of the system. Therefore, to extract the traces, we systematically apply all the recorded execution scenarios in the documentation.
4
Component Identification
As mentioned previously, the identification of components is modeled as a clustering problem. Indeed, the goal is to find the best partitioning Pi (V ) = {C1 , ..., Cn } where V is the set of classes in the system and a Cj is a a component candidate containing a subset of V . Pi (V ) needs to satisfy the following completeness and consistency properties:
220
S. Allier et al.
– 0<j≤n Cj = V (completeness) – ∀j, k|j = k, Cj ∩ Ck = {∅} (consistency) Exploring every possible clustering configuration cannot be done efficiently as we would need to consider every possible combination (NP-hard). We therefore propose using meta-heuristic search algorithms to find a near-optimal solutions. These algorithms are generic and are applied to a specific domain (in our case, graph partitioning) by defining the possible movements in the search space (transformations) and a fitness function that will be maximized. In our approach, the search for a component-based architecture is implemented using a hybrid search [8] which combines two different meta-heuristics: genetic algorithm (GA) and simulated annealing (SA). GA is a global search heuristic that applies changes to multiple solutions (called populations) and returns a solution that is globally near-optimal. This solution is then used as the initial solution of the SA algorithm, a local search algorithm. SA is called a local search algorithm because it explores the neighbourhood of a solution. Its output is our final solution. Both algorithms use the same solution representation and fitness function. 4.1
Solution and Space Representation
A solution for both algorithms can be any set of candidate components which respects the criteria of completeness and consistency. These candidates are represented by the set of classes they contain. In the example presented in Figure 2, the execution trace corresponds to a system with a set of classes A, B, C, D, E, F, H, I, J. One possible solution maybe: {{A, C, D}, {E, J}, {H, I, B, F }}. But there are many alternatives. In the following subsections we’ll see how to choose the best alternatives. 4.2
Fitness Function
The fitness function used (Equation 1) evaluates the quality of a partition considering both the internal cohesion of components and the level of inter-component coupling of every component C (C can also be seen as a set consisting of classes that it gathers). The function takes as input A (for architecture), the set of component candidates proposed by the solution, and calculates the weighed average of the fitness of individual components (Cl being the set of classes in the system). The fitness of individual components depends mostly on their cohesion (Equation 2) unless the coupling level is too high in which case the fitness score is heavily penalized. The threshold cm used corresponds to the average coupling of all the classes in the system. evalArch(A) =
1 (evalComp(C) ∗ |C|) |Cl|
(1)
C∈A
evalComp(C) =
evalCoh(C)/2 evalCoh(C)/2 + 0.5
if evalCoupling(C) < cm otherwise.
(2)
Restructuring Object-Oriented Applications
221
Cohesion. A good component should include classes that interact with one another to provide a specific set of functionalities; the strength of these interactions are what we call cohesion. The internal cohesion measure (Equation 3) evaluates how close are the different classes in the execution traces. It calculates the average distance between pairs of classes belonging to this component in all the traces. The distance between two classes a and b (Equation 4) is the average distance between instances of obj(a) of a and obj(b) of b. To reduce the complexity of exploring the traces when calculating distances (number of edges), we use a constant d which indicates the maximum interesting distance between two classes. In other words, if we consider that the distance in the call graph, between two classes, that is acceptable to consider is for example d = 5, then we normalize the actual distance n using d (dividing it by 5). For distances less than 5, it will weigh less than 1 (acceptable) and for 5 and more is given a weight of 1 (worst penalty). The distance distM in(C, x, b) is the minimal distance between a node corresponding to an instance x of the class a and any instance of the class b in the execution trace where x appears. If no path with a length less than d is found, the distance is considered as d. all the node of the path are an instance of a class of C and such as the leng of the path is lower to d, otherwise distM in(C, x, c) return d. ⎧ ⎪ ⎨ evalCoh(C, d) =
⎪ ⎩
|C|2
1 − |C|
dist(x, y, C)
1
dist(a, b, C) =
if |C| > 1 (3)
x∈C y∈C,y=x
if 1 d|obj(a)|
distM in(C, x, b)
|C| = 1 (4)
x∈obj(a)
The number of classes into the same component increases with d. The choice of the best value of d can be done with small examples of applications from the domain and some experience in the domain. Coupling. One of the strengths of component-based development is that its components are loosely coupled and can be mixed and matched to build systems. The function evalCoupling(C) (Equation 5 evaluates the level of coupling between components by counting the number of classes that are connected to a component (either calling or called). Classes that are part of the component are ignored. connected(x)| (5) evalCoupling(C) = | x∈C
For the example presented in Figure 3, the solution S = {{A, B, C}, {D, F }, {E, H, I, J}} would produce the following metrics with d = 3: cm = 2.66 evalCoh({A, B, C}) = 0.47 evalCoh({D, F }) = 0.33
222
S. Allier et al.
evalCoh({E, H, I, J}) = 0.7 evalCoupling({A, B, C}) = 2 evalCoupling({D, F }) = 2 evalCoupling({E, H, I, J}) = 4 evalArch(S) = ((0.47/2)∗3+(0.33/2)∗2+((0.7/2+0.5)∗4)) = 0.49 9 In the following, we’ll see how to determine the possible compositions. 4.3
Global Search
A genetic algorithm is a global meta-heuristics that emulates the concept of evolution. In order to find a solution from a population, it starts with a population (P0 ) containing a set of solutions (called chromosomes) and simulates the passing of generations on this population. This initial population is a randomly generated. For every iteration of the algorithm, a new population (Pi+1 ) is produced by selecting pairs of chromosomes ((c1 , c2 )) from Pi and applying a crossover and/or a mutation transformation to these pairs with a certain probability. For this work, we systematically add the best chromosome of a generation to the next generation. The precise algorithm (Algorithm 1) uses three inputs: M axIter, M axN iter and M axSize. M axIter is the maximum number of generation for the evolution. M axN iter defines maximum number of generations where no improvement is accepted. Finally, M axSize defines the maximum size for a population. Selection. The probability of selecting a chromosome c from the current population P depends on its quality with regards to the other chromosomes. This probability is given by the function: P s(c, P ) =
evalArch(c) a∈P evalArch(a)
This way of selecting components, called elitism, allows to give more chance to the fittest components to be selected. Crossover. The “classic” crossover transformation consists of spliting two chromosomes c1 and c2 into two parts and merge the first part of c1 (respectively the second part of c1 ) with the second part of c2 (respectively the first part of c2 ). However, this crossover might generate a solution that does not respect the constraints of completeness and consistency. Indeed, some classes could exist in more than one component in the new generated chromosomes or do not exist at all. To preserve the two above-mentioned properties, we propose the following variation: – Divide the chromosome c1 (respectively c2 ) into two parts c11 and c12 (respectively c21 and c22 ), each containing a subset of components. – Create a chromosome c1 by insert c11 between c21 and c22 (respectively c2 by inserting c21 between c11 and c12 )). – Delete in c21 and c22 all the classes appearing in c11 (respectively in c11 and c12 all the classes appearing in c22 ).
Restructuring Object-Oriented Applications
223
Algorithm: genetic(M axIter, M axN iter, M axSize) let iter := 0; niter := 0 create a initial population P0 let Best := minc∈P evalArch(c) while (iter < M axIter) and (niter < M axN iter) do eval Piter let Piter+1 := {∅} while Piter+1 < M axSize do Select c1 , c2 ∈ Piter Crossover c1 , c2 with probability pc to c1 , c2 Mutate c1 , c2 with probability pm to c1 , c2 Piter+1 := Piter+1 ∪ {c1 , c2 } end let BestLocal = minc∈Piter evalArch(c) Piter+1 := Piter+1 ∪ {BestLocal} if evalArch(BestLocal) < evalArch(Best) then Best := BestLocal niter := 0 end iter + +; niter + + end return Best
Algorithm 1. Genetic algorithm For example, the chromosomes: c1 = {{A, C, I}, {E, J}, {D, H, B, F }} c2 = {{A, H}, {B, C, D, E}, {F }, {I, J}} partitioned into: {{A,C,I}} and {{E, J}, {D, H, B, F }} for c1 , {{A,H}, {B,C,D,E} and {F }, {I, J}} for c2 . produces the two chromosomes: c1 = {{H}, {B, D, E}, {A,C,I}, {F }, {J}}, and c2 = {{I}, {A,H}, {B,C,D,E}{J}, {F }}. Mutation. There are three type of mutation applicable to a chromosome: – Split of a component in two components; – Merge of two components; – Move of a class from a component to another. The type of the mutation is selected randomly as they are components involved in the mutation. All three mutations produce solutions that preserve the properties of completeness and consistency. For example, c = {{A, C, D}, {E, J}, {B, F, H, I}} could be mutated in: cmerge = {{A, C, D}, {B, E, F, H, I, J}} or into cmove = {{A, C}, {D, E, J}, {B, F, H, I}}.
224
S. Allier et al. Algorithm: SimulatedAnnealing(s, T p, delta, tM in, iter, cof ) let Best := s while T p > tM in do for i = 0; i < iter; i + + do let sneigh := N eigh(s) let delta := evalArch(s) − evalArch(sneigh ) −delta
if (delta < 0) or (random < e T p ) then s := sneigh end if evalArch(sneigh ) < evalArch(Best) then Best := sneigh end T p := cof ∗ T p end end return Best
Algorithm 2. Simulated Annealing algorithm 4.4
Local Search
GA can explore different solutions in a large search space to produces a solution that is globally near-optimal. This solution is then used by SA as a starting point for a fine grained exploration of its neighbourhood with the objective of refining it. The algorithm is presented Algorithm in 2. SA manipulate only one solution (s) at a time. At each iteration of the algorithm, this solution is compared to a neighbour (sneigh ) generated by a function N eigh(x). When sneigh is better than s as measured by the fitness function (evalArch), it replaces it. Otherwise, it can be accepted with a small probability which decreases as the algorithm progresses. This element of randomness is included to avoid falling into a local optimum. Neighbour Function. The neighbourhood function (N eigh(s)) uses the mutation of the genetic algorithm to produce a neighbour.
5
Case Study
In this section, we present and discuss the results obtained on three systems of different size (respectively 40, 73 and 221 classes). 5.1
System Descriptions
Our approach was evaluate on three systems. The first is an interpreter of the language Logo1 . It has a graphical interface which allows writing the code and a window which shows the result graphically. This programs contains 40 classes. 1
http://naitan.free.fr/logo/
Restructuring Object-Oriented Applications
225
Jeval2 is the second program. It is an expression interpreter. It contains 73 classes. Finally the last system is Lucene3 , a high-performance, full-featured text search engine. Lucene contains 221 classes. 5.2
Extraction of Traces
The extraction of execution traces was implemented using MuTT [12]. MuTT is a Multi-Threaded Tracer built on the top of the Java Platform Debbuger Architecture. For a given program execution, MuTT generates an execution trace for each thread. For the three system, the extraction of the execution traces were generated as follows: – Logo: The execution traces were obtained by executing different scenarios of use case. The definition of use cases and execution scenarios was easy, because, one of the authors of this paper was in the development team of the Logo interpreter. – Jeval, Lucene: The execution traces of Jeval and Lucene were derived by executing different scenarios of use cases. Scenarios was obtained by the test cases defined for these systems. We ensured that the test cases cover well the use-case scenarios. Table 1 gives for every system the number of execution scenarios and the number of generated execution traces. There are more traces than scenarios because a trace is generated for each thread. Table 1. Capture of the executions traces
Logo Jeval Lucene
5.3
number of executions trace 8 19 9 9 19 59
Result
The identification results for the three systems are presented in Table 2. For each system, it shows the number of identified components and the numbers of those who are related to the application and those who are not. A component is related if it contains the classes that provide a system function. It is considered as not related otherwise. Logo Interpreter. This system produces 5 components. Component Library implements the basic functionality of the language Logo (Math, String, ...). Display is composed of the classes responsible for the display of the instructions of the 2 3
http://jeval.sourceforge.net http://lucene.apache.org/
226
S. Allier et al. Table 2. Results of component identification
Logo Jeval Lucene
number of component related not related 5 3 2 5 5 0 25 16 9
language Logo. Both components implement only one function. The third component Evaluator GUI provided two interrelated functions: the evaluation and the GUI for the result of the evaluation. The two other components do not contain functionally related classes. They both contain classes related to error management and other classes that plays the role of glue code between the three other components. Jeval. This system is partitioned into 5 components. Of these 5 components two represent respectively the library of the mathematical functions (sin, log, ...) and the library of the string functions. All the contained classes are related to the functions. The three other components contain classes necessary for respectively the parsing, the interpretation, and the mathematical operators evaluation. Depending on the viewpoint, these components could be merged. Lucene. From the 25 components, 16 are good and 9 bad. Four of the good components contain clear single functions. For example, QueryParser contain only classes responsible for the parsing of the search queries. The others 12 good components provide only one function, but with few classes missing. Here again, some could be merged if the goal is to obtained coarse-grained components. For example, the indexation function is split into subfunctions (5 components). Finally 9 identified components have no clear function. As mentioned in section 2, the goal of our identification process is to find group of classes that could be packaged for reuse purpose. It is then normal that some of identified components are not considered as good. When putting a threshold on EvalComp, almost all the bad components will not be considered. 5.4
Components as a Behavioral Understanding Aid
Identified candidate components could be used to understand the behavior of a system. In the case of Logo Interpreter, when classes are grouped by their corresponding components in the execution traces, one can understand the behavior of the system. Indeed, the obtained nodes in the traces represent the system functions and, the links represent the function interrelations. Each execution scenario is then associated with a component interaction scenario. To illustrate the behavior understanding process, let us take the example of the following use case scenario: Actor: Logo programmer Scenario: A1: Run the Logo interpreter
Restructuring Object-Oriented Applications
227
GUI
Initialisation Display
Library
Evaluator
Evaluation Library
Library
Display
GUI
Fig. 3. Execution trace
A2: Write the following code in the editor window point 1 1 write ”Hello” A3: Run the evaluation of the code from the editor window A4: Close the Logo interpreter The execution trace corresponding to this scenario is shown in Figure 3. In this trace the component Evaluator GUI was manually divided into two components Evaluator and GUI. This trace contains two different phases : initialization and evaluation. In the first phase, when the Logo interpreter is run the GUI triggers the initialization of components Evaluator Library and Display (drawing standard output). In the second phase, the typed code is parsed and evaluated (Evaluator). During the evaluation, Evaluator calls twice Library: the first time for the function point and second time for the function write. The function point call the component Display to display the point at the coordinate (1,1), and the function call the component GUI to print in the standard output the text “Hello”. 5.5
Discussion
The results of this case study are satisfactory. Indeed even if components identified are not all in the ”related” category, the majority provide a unique feature and by spliting or merging the others, it is easy to obtain ”related” component. Furthermore, this case study revealed a possible limitation of our approach.
228
S. Allier et al.
Indeed, our approach is designed to treat all the classes of the system as potential parts of components. It does not consider explicitly the case of glue-code classes. Detecting such classes and excluding them from the partitioning will certainly improve the identification results.
6
Related Work
The work proposed in this paper crosscuts three research areas: architecture recovery/remodularization, legacy software re-engineering, and feature location. Different approaches have been proposed to recover architectures from an object-oriented program. The Bunch algorithm [16] extracts the high-level architecture by clustering modules (files in C or class in C++ or Java) into subsystems based on module dependencies. The clustering is done using heuristicsearch algorithms. In [15], Medvidovic and Jakobac proposed the Focus approach whose goal is to extract logical architectures by observing the evolution of the considered systems. The approach identifies what the authors call processing and data components from reverse engineered class diagrams. Closer to our work, the ROMANTIC approach [2] extracts component-based architectures using a variant of the simulated annealing algorithm. In addition to dependencies between classes, other information sources are considered to help identifying functional components rather than logical sub-systems. Such sources include documentation and quality requirements. In [20], Washizaki and Fukazawa concentrate on the extraction of components by refactoring Java programs. This extraction is based on the class relation graphs. In the above-mentioned work, the component extraction process uses dependencies between classes/modules that are extracted using static analysis. Dependencies are not related to particular functions of the considered system which makes it difficult to relate identified components to specific functions. Thus, the components identified have a general scope and are not dedicated to the application. Whereas in the case of our approach, identification is guided by the context of the application, which will facilitate its maintenance. Our approach use heuristic-search methods [8], genetic algorithm and simulated annealing. Search-based methods is widely applied to solve problems similar to ours. For example, Seng [19] improves the design of an 00 code with a fitness function that quantify the improvements in design metrics. To this end, a genetic algorithm is used. In [9], Kessentini uses meta-heuristiques to transform models by examples. More close to our work, [2] use a variant of the simulated annealing for extract component-based architectures. Feature location is probably the problem that is closest to the one addressed in this paper. Many research contributions proposed solutions that are based on dynamic analysis [6] or combinations of static and dynamic analyses [3,12,17]. In general, static analysis uses call graphs and/or keyword querying. From the other hand, dynamic analysis consists in executing scenarios associated with features, collecting the correspondent computational units (PU), generally methods
Restructuring Object-Oriented Applications
229
or classes, and deciding which PU belongs to which feature. The decision can be made by metrics [6], FCA [3], or a probabilistic scoring [17]. Finally, sometimes, static analysis is used to enrich the dynamic analysis. Both analyses can also be performed independently and their results combined using a voting/weighting function. The combination of static and dynamic analyses is also used in a semiautomatic process where visualizations are proposed to experts to make their decisions [1]. Like in our case, the feature location approaches use dynamic analysis and try to associate program units to scenarios. The difference, however, is that the problem of locating features and identifying components are different in objectives and nature. In the first case, the goal is to determine code blocks, methods, or classes that are involved in a particular feature represented by a set of scenarios. For component identification, a scenario may involve many features (data acquisition, processing, data store, and results displaying). The association between feature and scenario is not a one-to-one relation. Moreover, the execution of a feature may necessitate the execution of many other features, which makes it difficult to draw the boundaries. For this reason, we view the component execution as sequences of interactions between classes in an integrated dynamic call graph.
7
Conclusion
Our main objective in restructuring an object-oriented application into a component-oriented application is the improvement of its evolvability. Thus, unlike other existing approaches where dependencies between classes are derived by static analysis, we used, for our component identification approach, method call trees obtained by executing use case scenarios on the application. This guarantees that only functional dependencies are considered in the component identification. Indeed, application’s evolutions have most often a functional scope. Moreover, the execution traces, obtained thanks to the use cases, limit the analysis of dependency only to the space covered by the application. While the classes, which are often generic, cover a wider space. In the past we presented a work that allows a company to organize its source code into reusable components [7]. This consisted in a reorganization of the development life-cycle and in the use of the UML2 component model in order to wrap a code corresponding to a component. Although in the case of our experimentation with our industrial partner, the engineers know very well the existing code, identification remains a tedious job. Moreover, in several cases of incorrect component identification, the cause was a reflex related to their long experience with the object-oriented approach. The work presented here is complementary to that presented above. It is an aid in identifying components by grouping classes. As our approach relies on execution traces of an application, the proposed grouping is necessarily adapted to this application. What goes in the direction of improving the maintainability of the application. In the case of our industrial partner, applications are built to the same scope (Geographical information systems) using their library of classes.
230
S. Allier et al.
To build their library of components, we should grouping classes by using traces from all their applications. After that, we plan to use another work, that we have already made, in order to automatically select components [5]. Our approach comes just before building the components. It only proposes the classes that must go together. Thus, its use is possible with any model of components.
References 1. Bohnet, J., D¨ ollner, J.: Visual exploration of function call graphs for feature location in complex software systems. In: SOFTVIS, pp. 95–104 (2006) 2. Chardigny, S., Seriai, A., Tamzalit, D., Oussalah, M.: Quality-driven extraction of a component-based architecture from an object-oriented system. In: CSMR, pp. 269–273 (2008) 3. Eisenbarth, T., Koschke, R., Simon, D.: Locating features in source code. IEEE Trans. Software Eng. 29(3), 210–224 (2003) 4. Erlikh, L.: Leveraging legacy system dollars for e-business. IEEE IT Professional 2(3) (2000) 5. George, B., Fleurquin, R., Sadou, S.: A methodological approach for selecting components in development and evolution process. Electronic Notes on Theoretical Computer Science (ENTCS) 6(2), 111–140 (2007) 6. Greevy, O., Ducasse, S.: Correlating features and code using a compact two-sided trace analysis approach. In: CSMR, pp. 314–323. IEEE Computer Society, Los Alamitos (2005) 7. Kadri, R., Merciol, F., Sadou, S.: CBSE in Small and Medium-Sized Enterprise: Experience Report. In: Gorton, I., Heineman, G.T., Crnkovi´c, I., Schmidt, H.W., Stafford, J.A., Szyperski, C., Wallnau, K. (eds.) CBSE 2006. LNCS, vol. 4063, pp. 154–165. Springer, Heidelberg (2006) 8. Kelner, V., Capitanescu, F., L´eonard, O., Wehenkel, L.: A hybrid optimization technique coupling an evolutionary and a local search algorithm. J. Comput. Appl. Math. 215(2), 448–456 (2008) 9. Kessentini, M., Sahraoui, H., Boukadoum, M.: Model transformation as an optimization problem. In: Czarnecki, K., Ober, I., Bruel, J.-M., Uhl, A., V¨ olter, M. (eds.) MODELS 2008. LNCS, vol. 5301, pp. 159–173. Springer, Heidelberg (2008) 10. Lehman, M., Belady, L.: Program evolution: Process of software change. Academic Press, London (1985) 11. Lientz, B.P., Swanson, E.B.: Problems in application software maintenance. Communiactions of the ACM 24(11) (1981) 12. Liu, D., Marcus, A., Poshyvanyk, D., Rajlich, V.: Feature location via information retrieval based filtering of a single scenario execution trace. In: Stirewalt, R.E.K., Egyed, A., Fischer, B. (eds.) ASE, pp. 234–243 (2007) 13. Maqbool, O., Babri, H.: Hierarchical clustering for software architecture recovery. IEEE Trans. Softw. Eng. 33(11), 759–780 (2007) 14. McKee, J.: Maintenance as function of design. In: AFIPS National Computer Conference, pp. 187–193 (1984) 15. Medvidovic, N., Jakobac, V.: Using software evolution to focus architectural recovery. Automated Software Engg. 13(2), 225–256 (2006)
Restructuring Object-Oriented Applications
231
16. Mitchell, B.S., Mancoridis, S.: On the evaluation of the bunch search-based software modularization algorithm. Soft. Comput. 12(1), 77–93 (2008) 17. Poshyvanyk, D., Gu´eh´eneuc, Y.-G., Marcus, A., Antoniol, G., Rajlich, V.: Feature location using probabilistic ranking of methods based on execution scenarios and information retrieval. IEEE Trans. Software Eng. 33(6), 420–432 (2007) 18. Seacord, R.C., Plakosh, D., Lewis, G.A.: Modernizing legacy systems: Software technologies, engineering processes, and business practices. SEI Series in Software Engineering (2003) 19. Seng, O., Stammel, J., Burkhart, D.: Search-based determination of refactorings for improving the class structure of object-oriented systems. In: GECCO, pp. 1909–1916. ACM, New York (2006) 20. Washizaki, H., Fukazawa, Y.: A technique for automatic component extraction from object-oriented programs by refactoring. Sci. Comput. Program. 56(1-2), 99–116 (2005)
(Behavioural) Design Patterns as Composition Operators Kung-Kiu Lau, Ioannis Ntalamagkas, Cuong M. Tran, and Tauseef Rana School of Computer Science, The University of Manchester Manchester M13 9PL, United Kingdom {kung-kiu,intalamagkas,ctran,ranat}@cs.manchester.ac.uk
Abstract. Design patterns are typically defined informally, albeit in a standard format, and have to be programmed by the software designer into each new application. Thus although patterns support solution reuse, in practice this does not translate into code reuse. In this paper we argue that to achieve code reuse, patterns should be defined and used in the context of software component models. We show how in such a model, behavioural patterns can be defined as composition operators which can be stored in a repository, alongside components, thus enabling code reuse.
1
Introduction
Design patterns [5], as generic reusable solutions to commonly occurring problems, are one of the most significant advances in software engineering to date, and have become indispensable tools for object-oriented software design. However, a pattern is typically only defined informally, using a standard format containing sections for pattern name, intent, motivation, structure, participants, etc. To use a pattern for an application, a programmer has to understand the description of the pattern and then work out how to program the pattern into the application. Although patterns are supposed to encourage code reuse (by way of solution reuse), in practice such reuse does not happen, since the programmer has to program the chosen pattern practically from scratch for each application. In this paper we argue that to really achieve code reuse, patterns should be defined and used in the context of software component models [8,19]. Moreover, patterns should be formal entities in their own right, so that they are units with their own identity that can be composed with specific components to form a solution to a specific problem. In other words, patterns should be explicitly defined composition operators (in a component model) that can be used to compose components. As composition operators, patterns would be like functions with generic parameters, and as such would be reusable with different sets of components for different problems. Furthermore, the semantics of a pattern can be defined formally, and then embodied in the corresponding composition operator, so that choosing a pattern can be done on the basis of formal semantics, rather than informal description, as is current practice. L. Grunske, R. Reussner, and F. Plasil (Eds.): CBSE 2010, LNCS 6092, pp. 232–251, 2010. c Springer-Verlag Berlin Heidelberg 2010
(Behavioural) Design Patterns as Composition Operators
233
In our work on software component models [18,15], we have defined (a component model with) explicit composition operators. Such operators can themselves be composed into composite operators [13]. In this paper, we show how we define composition operators, in particular composite ones, and how (some) behavioural patterns can be defined as such operators. We define the Chain of Responsibility (CoR) pattern as a basic composition operator, the Observer pattern as a composite composition operator, and a composite pattern composed from CoR and Observer as another composite operator composed from the two former operators. We also show an implementation in which patterns are stored in a repository, alongside components, thus enabling code reuse.
2
Related Work
Design patterns have been formalised by using some formalisation of objects and their relationships. For example, in [24] patterns are defined by using the object-oriented specification language DISCO [10]. These approaches basically take the informal description of a pattern, as given in e.g. [5], and re-write it in a formal manner. However, they do not define patterns as operators that can be applied to generic parameters. Consequently, the formalisation provides just another definition of patterns, and the programmer still has to program a chosen pattern from scratch for each application. Therefore there is no code reuse. Composite design patterns [33] and techniques for composing patterns have also been investigated. For example, the composite patterns active bridge, bureaucracy and model-view-controller were proposed in [27] Composition techniques can be classified as (i) stringing or (ii) overlapping [35,7]. In stringing, patterns are glued together; in overlapping, a participant in one pattern also participates in another pattern at the same time. In these techniques, composition is constrained by relationships between roles. For example, design patterns that are architectural fragments are composed by merging roles in [1] using superimposition. However, these techniques are defined informally, and are applied in an ad hoc manner. Therefore, they do not support systematic code reuse. To achieve code reuse in a more direct manner, there has been research into componentising patterns, by implementing packages for patterns that programmers can use to program patterns for different applications. For example, [23] shows that two thirds of the common patterns like Visitor can be ‘componentised’ in this way. Patterns are implemented as Eiffel packages that require other Eiffel packages. However, in a pattern package, the roles of and the constraints on the participant objects are not (cannot be) specified. As a result, a package does not truly represent a pattern. Although some code reuse is achieved by the use of skeleton code provided by packages, most of the coding effort remains, in partcular for code that defines the participants’ roles and constraints. Component composition patterns were identified in [34] to define domainspecific communication patterns using modified sequence diagrams. Component roles are used to restrict the behaviour of the participating components, but in terms of their interface behaviour. The focus of this work is on the definition of
234
K.-K. Lau et al.
domain-specific communication patterns and not on generic software patterns, and pattern composition is undefined.
3
Our Approach
We believe that true code reuse can be achieved by defining design patterns in the context of a properly defined software component model. Such a model defines what components are, and mechanisms for composing them. A generic view of a component is a composition unit with required services and provided services. Following UML2.0 [25], this is expressed as a box with lollipops (provided services) and sockets (required services), as shown in Fig.1(a). In current software component models [19], components are either objects or architectural units. Exemplars of these models are EJB [4] and ADLs (architecture description languages) [21] respectively. An object normally does not have an interface, i.e. it does not specify its required services or its provided services (methods), but in component models like JavaBeans [29] and EJB, beans are objects with an interface showing its provided methods but usually not its required services (Fig.1(b)). Architectural units have input ports as required services and output ports as provided services (Fig.1(c).) Therefore, objects and architectural units can both be represented as components of the form in Fig.1(a). Required service Provided service
(a) A generic component
input
output
Provided method
(b) An object
(c) An architectural unit
Fig. 1. Components
Objects and architectural units are composed by connection (Fig.2), whereby matching provided and required services are connected by assembly connectors. In order to get a required service from another object, an object calls the appropriate method in that object. Thus objects are connected by method delegation, i.e. by direct message passing. For architectural units, connectors between ports provide communication channels for inAssembly connector direct message passing.1 We have defined a component model [18,15] in Fig. 2. Connection which composition operators are explicitly defined entities with their own identities. In our model, components are encapsulated : they encapsulate control, data as well as computation, as in ‘enclosure in a capsule’. Our components have no external dependencies, and can therefore be depicted as shown in Fig.3(a), with just a lollipop, and no socket. There are two basic types of components: (i) atomic and (ii) composite. 1
In [30] object delegation and architectural unit composition are differentiated.
(Behavioural) Design Patterns as Composition Operators
SEQ
IU U
(a)
Atomic component
235
(b) Composition connector
(c)
IA
IB
A
B Composite component
ATM (d)
BB
Bank system
Fig. 3. Our component model
Fig 3(a) shows an atomic component. This consists of a computation unit (U) and an invocation connector (IU). A computation unit contains a set of methods which do not invoke methods in the computation units of other components; it therefore encapsulates computation. An invocation connector passes control (and input parameters) received from outside the component to the computation unit to invoke a chosen method, and after the execution of method passes control (and results) back to whence it came, outside the component. It therefore encapsulates control. A composite component is built from atomic components by using a composition connector. Fig.3(b) shows a composition connector. This encapsulates a control structure, e.g. sequencing, branching, or looping, that connects the subcomponents to the interface of the composite component (Fig.3(c)). Since the atomic components encapsulate computation and control, so does the composite component. Our components therefore encapsulate control (and computation)2 at every level of composition. Note that we have emphasised the significance of control encapsulation in [16]. Our components can be active or passive. Active components have their own threads and execute autonomously, whereas passive components only execute when invoked by an external agent. In typical software applications, a system consists of a ‘main’ component that initiates control in the system, as well as components that provide services when invoked, either by the ‘main’ component or by the other components. The ‘main’ component is active, while the other components are passive. For simplicity, in this paper we focus on passive components in our model; composition of active components is much more involved, by comparison (see [14]). In our model, passive components receive control from, and return it, to connectors. In a system, control flow starts from the top-level (composition) connector. Fig.3(d) shows a simplified bank system with two components ATM and BB (bank branch), composed by a sequencer composition connector SEQ. Control starts when the customer keys in his PIN and the operation he wishes to carry out. The connector SEQ passes control to ATM , which checks the customer’s PIN; then it passes control to BB, which gets hold of the customer 2
As well as data [17].
236
K.-K. Lau et al.
account details and performs the requested operation. Control then passes back to the customer. In summary, composition in our model is hierarchical: components can be ‘recursively’ composed into larger composites, as can be seen in Figs. 3(c) and 3(d).
4
Composition Operators
In [18] we defined the composition operators informally, and in [15] we defined them in terms of many-sorted first-order logic. In addition, we defined a catalog of composition operators in [32]. To relate our composition operators to behavioural patterns, in this section we give the formal semantics of composition operators in terms of Coloured Petri nets [11]. First, it is worth emphasising that, as we saw in Section 3, our composition operators are connectors [18] that encapsulate control. Moreover, these operators themselves can be composed to yield composite operators that also encapsulate control. This is illustrated in Fig.4 for one thread of control for the sequencer composition operator. For brevity, we will refer to composition operators simply as connectors.
control flow
Fig. 4. Control encapsulation
4.1
Connector Template Nets
We will define our connectors as a special kind of Coloured Petri net [11], which we call a Connector Template net (CT-net). A connector in our model is of arbitrary arity and parametricity, and therefore cannot be defined directly using Coloured Petri nets. A Petri net3 is a set of places (with tokens) and transitions connected by arcs with expressions; and a Coloured Petri net is a Petri net in which the tokens can be of different types (colours). Definition 1. A Connector Template net (CT-net) is a tuple (N, Ar, Σ, P, T, A, C, G, E, I, In, Out, CP ), where: (i) N is the unique name of the CT-net. (ii) Ar is an expression (containing at least one variable) defining the arity of the connector. (iii) Σ is the colour set defining the types used in the CT-net, P , T , A are disjoint sets of places, transitions and arcs, where P and T are basic sets, whereas A is of type P × T ∪ T × P . 3
We assume familiarity with Petri nets.
(Behavioural) Design Patterns as Composition Operators
237
(iv) C is a function defining the types for each place in P , G defines guard expressions for transitions in T , E is a function defining the arc expressions in A, and I defines the initial marking for each place in P . (v) In and Out are distinguished input and output places of the CT-net, s.t. {In, Out} ⊂ P ∧• In = ∅ ∧ Out• = ∅, where • n and n• denotes the preset and the postset of n, i.e. the set of nodes in P ∪T such that there is an arc from a node in the set to n, or from n to some node in the set respectively. (vi) CP is the distinguished set of composition places of the CT-net, s.t. CP ⊂ P ∧ #CP = Ar ∧ ∀cp ∈ CP, #•cp = #cp• , i.e. the cardinality of composition places equals the arity Ar of the connector, and the number of input transitions to each composition place equals the number of output transitions of the same place. For simplicity, we have defined the arc set A in a CT-net as pairs of nodes (places or transitions), in part (iii) of Definition 1. This introduces the limitation that between each pair of nodes we can define at most one arc, whereas in Coloured Petri nets multiple arcs are allowed. However, this limitation poses no problems since multiple arcs for a pair of nodes can always be merged into a single arc [11]. Graphically, a CT-net can be depicted as in Fig.5. It has a set of distinguished places: an input place In, an output place Out, and a set of composition places CP1 , . . . , CPn , connected to transitions (boxes) in a Coloured Petri net (the dotted box). Each composition place CPi represents a connector or a component, and has precisely one incoming ini and one outgoing arc outi . A CT-net encapsulates control that flows in through its input place, its internal Coloured Petri net, its composition places, back through the internal Coloured Petri net, and finally out through its output place. Control encapsulation in a CT-net in Fig.5 is therefore the same as that defined in Fig.4 for a (composite) connector, for each thread of control. Concretely we will only use CT-nets with fixed arities defined in a toolkit called CPN Tools [3] for Coloured Petri nets. In these concrete CT-nets, places (and hence tokens) are of type N ×CID, where N is the type of natural numbers, and CID is the cartesian product of two integer types. CID is a case identifier that distinguishes between different (initial) threads corresponding to requests In
Out
... in 1
... out 1 in n
CP1
...
out n CPn
Fig. 5. A CT-net
238
K.-K. Lau et al.
In
Out
(j,c) T11 (j,c)
(j,c) T12 (j,c) (j,c)
CP11
T13 (j,c) CP12
In
Out
(j,c)
(j,c)
T21 (j,c) CP21
(a) (Binary) Pipe/Sequencer
(j,c)
T22 (j+1,c)
(j+1,c)
CP 22
(b) (Binary) Cobegin
Fig. 6. Basic composition operators
by different “users” of a connector, and N is used to identify different sub-threads (see the discussion on the Cobegin connector below). Now we show how connectors in our component model can be defined as CT-nets. We distinguish between basic and composite connectors. 4.2
Basic Composition Operators
Basic connectors in our model are connectors for sequencing, branching and (finite) looping. Fig.6 shows the CT-nets for the Pipe connector (for sequencing) and the Cobegin connector (for branching)4 . The Pipe connector receives requests and passes them to the components sequentially. The pipe also enables passing results from one component to the next. The CT-net for Pipe is the same as that for the Sequencer connector. The Sequencer is the same as the Pipe except it does not pass results. The Cobegin connector splits each incoming thread into 2 sub-threads (sharing the same CID), that execute concurrently the connected components. In terms of CT-nets, every basic connector has the following property: Property 1. The control flow of each connector guarantees that each token in the input place will eventually flow to the output place of the connector. In the output place there can only appear the tokens that have previously appeared in the input place, and for each token in the input place there will be exactly one token in the output place. This property simply states that incoming control threads do not vanish during connector execution, and only these threads return as a result of the connector execution. This property can be trivially proved for the basic connectors, and it must be preserved during connector composition. 4.3
Composite Composition Operators
Connectors can be composed via their input, output and composition places: a composition replaces a composition place by its matching input and output places, and re-directing its in-arc to the input place and its out-arc from the output place respectively. This is illustrated in Fig.7, where the two CT-nets are 4
For simplicity, we only consider binary connectors.
(Behavioural) Design Patterns as Composition Operators
In In
Out
Out
... in 11
... out 11 in 1m
CP11
...
out 1m
CP1m
... in 11
... out 11 in 1m
CP11 In
out 1m
...
Out
... in 21
239
... out 21 in 2n
CP21
...
out 2n CP2n
... in 21
... out 21 in 2n
CP21
...
out 2n CP2n
Fig. 7. Composing connectors
composed by matching the composition place CP1m in the first CT-net with the input and output places of the second CT-net. The in-arc in1m of CP1m is redirected to the input place, whilst the out-arc out1m is re-directed from the output place, of the second CT-net. The resulting composite connector has the input and output places of the first CT-net; the composition places CP11 , . . . , CP1(m−1) of the first CT-net and CP21 , . . . , CP2n of the second CT-net. (The input and output places in the second CT-net become dummy places in the composite CTnet.) An example of CT-net composition can be seen in Fig.11, where the Pipe and Cobegin CT-nets from Fig.6 are composed. Clearly composition of CT-nets is hierarchical. The resulting composite connector is thus self-similar to its sub-connectors, i.e. it has the same structure as each of the latter. This self-similarity is significant because it means that the composition of connectors is strictly hierarchical; it is also algebraic because a composite connector also encapsulates control, just like its sub-connectors. Indeed, composite connectors have the following property: Property 2. Property 1 holds for composite connectors that are composed from primitive operators when no places are shared during composition. Thus composite connectors can be used in further composition, just like the basic connectors. This is because their control flow is similar to that of the latter and it guarantees that the only control threads returned are the ones that are given as input to the connector. For self-similarity, the proviso of no shared places during composition must be observed. However, this can be overcome by the use of dummy places that serve as ‘memory’ places that retain copies of tokens and thus simulate non-sharing of places.
240
5
K.-K. Lau et al.
Behavioural Patterns
We have seen that our connectors encapsulate control, and can be composed into composite connectors. In this section we will show that because they encapsulate control and are generic in terms of arity and parametricity, they can be used to define design In Out patterns [5], more precisely, behavioural patterns. We will show that even a basic connector can be used to define a pattern; whilst a composite connector can be ... ... used to define a more complicated pattern. Specifically, a connector can only define the control Fig. 8. A component flow in a pattern; it cannot specify the participants and their roles in the pattern. The participants are of course components, so we need to consider how components are defined and how they are composed by connectors. A component is defined as a net with distinguished Input and Output places (Fig.8) connected to transitions (boxes) in a Coloured Petri net (dotted box). Such a net is the same as a CT-net (Fig.5) except that it has no composition places. Clearly, like a CT-net, a component net can be composed with a CT-net via the latter’s composition places. To use a CT-net to define a pattern, we need to add constraints that specify the components that participate in the pattern, and their roles, to ensure conformance with the semantics of the pattern. In addition we could also have constraints on the CT-net itself (as we will see later). Thus a pattern is a pair (CT-net, constraints), where constraints specify the participating components and their roles, and possibly also some restrictions on the CT-net. In other words, just as a (concrete) CT-net is an instance of a template (specified by its arity), a pattern is an instance of a CT-net (specified by its constraints). 5.1
Constraints
The Coloured Petri net in a component net (Fig.8) represents the behaviour of the methods5 in the computation units of the component. Therefore, in a pattern, the constraints on the participating components and their roles are expressed in terms of the names and types of the methods and their parameters in these units. We denote these constraints by C. Constraints on the CT-net in a pattern are constraints on the composition places in the CT-net that are instantiated by the components composed by the CT-net. These can express restrictions on, or adaptations of, the control flow in the CT-net, e.g. adding guards or conditional branching, and can alter the control flow in and out of a composition place. We will denote these constraints by D. Thus a pattern is (CT-net, (C, D)). C has to specify the roles of the participants as well as the relationships between the participants. We will define C as a pair of pre- and post-conditions p and q, i.e. C = (p, q): 5
These methods have pre- and post-conditions.
(Behavioural) Design Patterns as Composition Operators
241
(i) The pre-condition p specifies type conditions on the names and parameters of methods in the computation units of the participating components, as well as the relationships between these names and parameters. (ii) The post-condition q specifies the expected behaviour of the pattern P , in terms of the expected behaviour of each participating component. A pattern P = (CT-net, (C, D)) can only be applied as a composition operator to a set of components if the components collectively satisfy the pre-condition p in C. Satisfaction is checked by matching p with the pre-conditions of the methods in the computation units of the participating components. For valid components, the pattern P acts a composition operator with an adaptation by D of the control flow of CT-net. We have defined a constraint language for patterns. For C constraints, our language has similarities with OCL [26], a constraint language for objects in UML; however, unlike OCL, our language can also be used to define D constraints. For lack of space, we do not give full details of our constraint language, and will only give and explain some of the constraints that we will use. To implement a pattern P = (CT-net, (C, D)), we need to combine the semantics of CT-nets and the semantics of our constraint language. The D constraints in our constraint language can be implemented in a straightforward manner, since they define control structures which can be easily implemented in a programming language. In contrast, ‘implementing’ C constraints amounts to automatic specification matching and verification. This requires theorem proving and is undecidable in general. So in our implementation (see Section 6) C constraints are annotations that require manual checking. 5.2
Basic Composition Operators
Even a basic connector can be used to define a pattern. Consider the Sequencer connector. Its CT-net is shown in Fig.6(a), and Fig.9(a) shows the control flow it encapsulates. By defining suitable constraints we can use the Sequencer to define the Chain of Responsibility (CoR) pattern. According to its description in [5], the intent of CoR is to “avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request; chain the receiving objects and pass the request along the chain until an object handles it”. So to define the CoR using Sequencer, we need to allow two different control flows, depending on whether control exits after the
(a) Sequencer
(b) Chain of responsibility
Fig. 9. Chain of responsibility
242
K.-K. Lau et al.
first or the second component successfully handles the request. This is shown in Fig.9(b). We define CoR = (CT-net for Sequencer, C, D), where C and D are defined as follows. For simplicity, we continue to use binary connectors. Therefore, the CoR pattern, applied to two components C1 and C2, requires that the second component C2 can be used instead of the first one C1. For that reason we define the notion of behavioural conformance between the two components [20]. Specifically, for each method in C1, C2 must provide a method with the same i/o parameters, a weaker pre-condition and a stronger post-condition. Thus the pre-condition for CoR, in our constraint language, is: C1.methods->forAll(m1:Method | C2.methods->exists(m2:Method | (m2.input = m1.input and m2.output = m1.output and m1.Pre implies m2.Pre and m2.Post implies m1.Post))
The post-condition of the CoR ensures that whenever a method m in a component is invoked, the post-condition of m is satisfied. C1.methods(invoke).Pre implies C1.methods(invoke).Post or C2.methods(invoke).Pre implies C2.methods(invoke).Post
We need a D constraint that specifies that control reaches the next composition place iff the pre-condition of the component in the current composition place with the given input parameters is satisfied. This is defined as: if(eval(cp(currentIdx).methods(target).Pre,input.value)) then return cp(currentIdx).methods(target).output.value endif
5.3
Composite Composition Operators
A composite connector can also be used to define a pattern. Consider the composition of the Pipe and Cobegin connectors (defined in Fig.6). The resulting composite connector is shown in Fig 10. It passes results from P to S1 and S2. By defining suitable constraints, we can use this composite connector to define the Observer pattern. According to its description in [5], the intent of the Pipe Cobegin P
S1
S2
Fig. 10. ‘Observer’
(Behavioural) Design Patterns as Composition Operators
In
243
Out
(j,c)
(j,c)
T11 (j,c)
T12
T13
(j,c) (j,c) CP11
(j,c)
(j,c) T21
(j,c) (j,c)
(j+1,c)
(j,c) CP21
T22
(j+1,c)
CP22
Fig. 11. CT-net for Observer
Observer pattern is to “define a one-to-many dependency between objects so that when one object changes state, all its dependants are notified and updated automatically”. Therefore the composite connector in Fig.10 acts as an Observer, with a publisher P and two subscribers S1 and S2.6 So we define Observer = (CT-net for Pipe + CT-net for Cobegin, C, D). The composition of the CT-nets for Pipe and Cobegin is shown in Fig.11. This is the CT-net for Observer. The Observer composed from binary Pipe and binary Cobegin has three composition places; it is therefore ternary. In addition, C constraint is defined as follows. The pre-condition of the (ternary) Observer (applied to components C1, C2, C3) requires that some of the methods from the publisher component C1 can be matched by the methods of both the subscribers C2 and C3. This means that the output of C1 can be consumed as the input of C2 and C3. Therefore the pre-condition for Observer is: let M, M1: Set(Method) M1 = C1.methods(all)->select(m:Method | m.Post implies (length(m.output) > 0)) M = M1->select(m1:Method | C2.methods->exists(m2:Method | m2.input includes m1.output) and C3.methods->exists(m3:Method | m3.input includes m1.output)) M->size()>0
The post-condition of Observer ensures that the output of the publisher C1 will actually be used as (part of) the input to both the subscribers C2 and C3. We describe the part of the post-condition that applies to C2 (a similar one applies to C3). This post-condition is defined as: 6
Of course it would be better if P was an active component.
244
K.-K. Lau et al.
let pos:Integer pos=C2.methods(invoke).input->indexOf(C1.methods(invoke).output) C2.methods(invoke).input(pos .. (pos + length(C1.methods(invoke).output))).value = C1.methods(invoke).output.value
The D constraint for Observer is simply empty because the control flow defined by the composite composition connector already satisfies the pattern. 5.4
Composing Behavioural Patterns
Defining behavioural patterns as connectors offers the immediate benefit of being able to compose patterns in the same manner that we compose any connectors. However, constraints must be composed correctly. For two patterns P1 = (CTnet1, C1 , D1 ) and P2 = (CT-net2, C2 , D2 ), the resulting composite pattern has C1 ∧ C2 (with renaming) as its C constraints, and has D1 and D2 as its D constraints on CT-net1 and CT-net2 respectively. This kind of compositionality is a result of encapsulation in our model. For example, we can compose the Observer and CoR patterns into a composite (Fig.12). This composition connector connects a chain of publishers (P 1 and P 2) in a CoR to a set of subscribers (S1 and S2). This composite pattern extends the Observer to multiple publishers. The subscribers are however only interested in the first result, produced by (any of) the publishers. For instance, this pattern may apply to a scenario in which news subscribers wish to receive the first available news bulletin on a particular topic, published by any (of a set of) news agencies they subscribe to. Observer CoR P1
P2
S1
S2
Fig. 12. ‘CoR-Observer’
We have defined the composite pattern CoR-Observer with four composition places. The first two, C1 and C2, correspond to the publishers which are constrained by the CoR, whilst the last two, C3 and C4, correspond to the subscribers. Consequently the pre-condition of CoR-Observer must require C1 and C2 to act as publishers in a chain of responsibility. We only need to describe the requirements for C1 w.r.t. the Observer’s requirements. This is because in CoR C2 offers more than C1 and requires less than C1. The pre-condition is: let M, M1: Set(Method) M1 = C1.methods(all) -> select(m1:Method | m1.Post implies (length(m1.output) > 0))
(Behavioural) Design Patterns as Composition Operators
245
M = M1 -> select(m1:Method | C3.methods->exists(m3:Method | m3.input includes m1.output) and C4.methods->exists(m4:Method | m4.input includes m1.output)) M->size()>0 and C1.methods->forAll(m1:Method | C2.methods->exists(m2:Method | m2.input = m1.input and m2.output = m1.output and m1.Pre implies m2.Pre and m2.Post implies m1.Post))
The above conditions specify that the methods of C1 must provide some output and this must be acceptable by the C3, C4 components (a requirement for the Observer pattern). It also means that C2 can be used instead of C1. The post-condition of CoR-Observer states that when C1 gets invoked its output is consumed by the subscribers, and similarly for C2. The D constraint for CoR-Observer is simply the D constraint for the first two composition places: let currentIdx : int with 1..2 if(eval(cp(currentIdx).methods(target).Pre,input.value)) then return cp(currentIdx).methods(target).output.value endif
6
Implementation and Example
The CT-nets we have been using are defined in CPN Tools. Connectors could therefore be defined and composed using CPN Tools. However, CPN Tools cannot handle connectors that are patterns, because of the associated constraints. Therefore, we need to implement connectors ourselves, in a tool that can implement CT-nets as well as constraints for connectors that are patterns. In any case, for connectors (patterns) to be useful, we also need to implement components. So we have started to implement a tool for our component model. The tool supports the idealised component life cycle [19] consisting of: (i) design phase, in which components and connectors are designed, implemented and deposited in a repository; (ii) deployment phase, in which components and connectors are deployed into a specific application under construction. The tool therefore consists of a builder for the design phase, and an assembler for the deployment phase. In the builder, connectors and patterns can be defined and stored in a repository, alongside components. Basic connectors are defined first, and then used to define composite connectors. Design patterns can be defined from basic or composite connectors that have already been defined. By storing all the connectors in the repository, we can reuse them in the deployment phase for many different applications. In this way, as reusable connectors that can be retrieved from a repository, so design patterns become really reusable. Basic connectors are implemented as Java classes. For composite connectors, we define a connector composition language which is based on XML, which allows us to define the structure of a composite connector in terms of smaller
246
K.-K. Lau et al.
(a) Defining the Observer pattern.
(b) Building the system using patterns.
Fig. 13. Our prototype tool
connectors. We then implement a Java class that takes such structural definitions and realises the desired behaviour through object aggregation and composition of connector objects. Pattern definition in our repository consists of a connector definition and its associated constraints. For pre-defined (existing) connectors, the connector definition is a unique string for every connector. For (new) composite connectors, the structural definition must be given. The constraints for the connector are then defined and tied to the connector definition. D constraints can affect the control flow of our connectors, and therefore must be executed at runtime. Therefore, in our implementation, D constraints are transformed into Java code that is used to generate Java classes to realise the pattern. Indeed, the transformation is possible because D constraints are actually control flow structures, e.g. if-endif in the D constraints of CoR. The Java code in this case is the try-catch structure which captures a special exception thrown by the violation of the pre-condition of a component, before invoking the next component. This is because methods in a component throw a special type of exception if their pre-condition is violated. Thus the code of CoR pattern class has the code of the Sequencer connector class and the try-catch code realising the D constraint. Consider the Observer pattern. Fig.13(a) shows its definition using the builder tool. The XML definition consists of two sections that are identified by and tags for composing two basic connectors and defining constraints. It is clear that a binary pipe (pipe1 ) and a binary cobegin (cb2 ) are declared. The connector pipe1 thus has two composition places, and the connector cb2 replaces the second composition place, and is thus composed with pipe1. The pre-condition of the constraint is also shown in the figure; it is defined and constructed manually. Once defined, Observer pattern definition is stored into our repository as can be seen in Fig.13(b) (bottom right corner). CoR and composite patterns can be defined in a similar way and stored in the repository.
(Behavioural) Design Patterns as Composition Operators
247
A pattern, like any connector, can be used to compose components that are valid for the pattern. For example the CoR-Observer composite pattern can be used to build a news system by composing publisher components which are news agencies and subscriber components which are news companies that print news journals. Fig.13(b) shows an example with two news agencies, one national and one global, and two news companies, one printing newspapers and one printing magazines. The news agencies form a CoR and publish news whenever it became available. The news companies simply pick up the news on a topic of interest to them, from the first news agency that can supply that piece of news. In design phase we build the four atomic components which we then store into the repository. For each atomic component, its XML specification is defined and its computation unit is implemented. We only discuss the NationalNewsAgency component, as the other components are similar. Below we present the XML specification of the NationalNewsAgency component: NationalNewsAgency <Method> publishEconomicNews ... ... ... ... <require>hasMoreEconomicNews==true
economicNews != null ...
The national news agency can publish either economic or sports news. The pre-condition for the economic news states that there is some economic news available. If the pre-condition is satisfied then the result will be non-null. The computation unit of this component is implemented as a Jar file. The implementation indeed realises the component’s XML specification. We experimented with JML [12] for annotating our computation units so that designated exceptions are thrown to signify pre- and post-condition violations. At run-time, as the component is composed with CoR, we need to check that before calling publishEconomicNews, its pre-conditions (as specified in the XML description above) are satisfied. As we explained earlier we currently rely on runtime exceptions to check that. If the exception is not thrown, the CoR source code returns with valid output. Otherwise, CoR invokes the next component in the chain. Similar holds for post-conditions. The implementation of the D constraints in the source code of the CoR is outlined as follows: // References of components e.g. C1 & C2 private List comps = ... boolean success = false;
248
K.-K. Lau et al.
// Invoke the component in sequence as long as the previous // invocation fails because of pre-condition is violated for (i=0; i < comps.size() && success == false; i++) { try { Object[] res = comps.get(i).invoke(...); success = true; } catch (PreViolatedException pve) { ... } ...
The Java code snippet enables CoR to return control (and data) when an invocation is successful, i.e. PreViolatedException exception is not thrown. The XML specification and the Java implementation for the observer components (the magazine and the newspaper) are defined similarly to the above and due to lack of space we do not present them here. When all participating components have been defined, before using them into a composition with the CoR-Observer, we (manually) check that the preconditions of the CoR-Observer can be satisfied. First, we check that the global news agency conforms to the national one. Because this condition is satisfied, the components form a chain of responsibility. Additionally, the local agency can be used for publishing news to the magazine and newspaper observers because we map publishEconomicNews and publishSportsNews to getEconomicNews and getSportsNews respectively. Since the pre-conditions of the CoR-Observer have been satisfied, its post- conditions ensure that the resulting news of the agency that are published first, are transferred successfully to both observers. Based on the CoR-Observer composition connector and on the atomic components used, a composite component NewsSystem is created that will have two methods, observeSportsNews() and observeEconomicNews() as in Fig.13(b).
7
Discussion
The main contribution of this paper is to show how behavioural patterns can be defined and implemented as explicit entities with their own identities that can be deposited in a repository, and reused as often as necessary. This is novel, as far as we are aware, compared to related work in design patterns and software components. By defining behavioural patterns as composition operators, we have retained the original semantics intended for patterns as defined in [5], i.e. as reusable solutions that can be customised to different circumstances. Our composition operators for patterns are of generic arities, and can be applied to any components that satisfy the constraints. Each application of an operator to a selected set of components represents a customisation of the solution to the context of these components. Furthermore, our approach (and tool) can be used to define arbitrary behavioural patterns, and not just the ones that are already known in the literature [5]. More precisely, we can define behavioural patterns that involve pre-determined interactions between multiple participating components. Among
(Behavioural) Design Patterns as Composition Operators
249
existing behavioural patterns in [5], besides CoR and Observer, such patterns also include Visitor, State and Strategy. Hence, we can define more composite design patterns such as Strategy-Observer and State-Observer (which extend Observer to multiple publishers but with different publisher selection strategies), and Strategy-Visitor and State-Visitor (which can extend the Visitor pattern with many visitees and visitors), etc. Behavioural patterns with arbitrary interactions, e.g. Iterator, Mediator and Memento, however, cannot be pre-defined as connectors (and deposited in a repository) in design phase. In our component model, such patterns are purely deployment phase artefacts. They have to be defined ad hoc from our basic connectors like Sequencer, Selector, etc., anew for each application. Also in our component model, structural patterns also belong to this category because they can define arbitrary behaviour e.g. Facade and Adapter. Note that, this could involve using stateful versions of our connectors and adapters. Behavioural patterns that involve only one participant or define no interaction between participants, e.g. Template Method and Interpreter, do not require any composition, and as such they cannot be defined as connectors. Our approach currently requires manual checking of C constraints. Therefore, patterns need to be used and checked manually. This is a hindrance. However, the effort needed for creating a pattern is a one-off effort, and so it should still pay dividends because of it reusability. Moreover, in future we intend to enhance our C constraints with semantic annotations on component interfaces, and thereby automate constraint checking by implementing a suitable parser and evaluator. We will also study design patterns in a wider scope in order to seek new pattern connectors, thus extending our catalogue of connectors as patterns. Modelling component-based systems using Petri nets has led to various extensions to Petri nets for different kinds of components. For example, service nets [6] are used to describe the behaviour of a web service. Compared to CTnets, a service net also has input and output places, but not composition places. Composition operators for service nets are not nets themselves, but just rules for obtaining the service net of a composite service from the service nets of the subservices. In [9] component templates are defined as an extension of Petri nets for describing the behaviour of components. However, no composition operators are defined. Rather, composition occurs via embedding. In [31] Template Coloured Petri nets (TP-nets) are defined for specifying the behaviour of components. Components are processes in a message passing environment such as MPI [22]. However, composition of TP-nets is defined according to a pre-defined script and not according to composition operators. Coloured Petri nets have been used for modelling patterns of control for workflow systems [28]. This work is very similar to ours in that our CT-nets define patterns of control for component-based systems. However, they do not define composition for patterns. Compositionality of patterns is claimed, but this is actually an ad hoc combination, where places and transitions are arbitrarily connected and/or merged.
250
8
K.-K. Lau et al.
Conclusion
In this paper, we have presented an approach to behavioural patterns that we believe can achieve reuse at the levels of patterns as well as code. This is an advance on the state-of-the-art as far as we are aware. Our implementation is at a preliminary stage, but initial results have provided proof of concept. This has encouraged us to continue to expand our catalogue of connectors and patterns in the builder, with a view to tackling large scale applications in due course. Such applications will allow us to validate our approach, and provide a more convincing case for its practicability and scalability. We have not investigated connectors for dynamic or run-time composition. Currently our component model defines composition in design and deployment phases, but not dynamic composition at run-time. Composition is static mainly because we insist on defining composition operators. Such operators are harder to define for run-time phase, and so far we have not investigated them. Another future direction that we would like to pursue is to investigate the definition and use of patterns in specific domains. In this context, we will develop our approach in the European industrial project CESAR [2]. In particular, we will use it to provide design patterns for efficient composition of components into embedded systems in the avionics domain.
References 1. Bosch, J.: Specifying frameworks and design patterns as architectural fragments. In: TOOLS 1998, p. 268. IEEE Computer Society, Los Alamitos (1998) 2. CESAR project, http://www.cesarproject.eu/ 3. Denmark CPN Group, University of Aarhus. CPN tools - computer tool for Coloured Petri Nets, http://wiki.daimi.au.dk/cpntools/cpntools.wiki 4. DeMichiel, L., Keith, M.: Enterprise JavaBeans 3.0. Sun Microsystems (2006) 5. Gamma, E., Helm, R., Johnson, R., Vlissides, J.: Design Patterns – Elements of Reusable Object-Oriented Design. Addison-Wesley, Reading (1995) 6. Hamadi, R., Benatallah, B.: A Petri net-based model for web service composition. In: Proc. 14th Australasian Database Conf., pp. 191–200 (2003) 7. Hammouda, I., Koskimies, K.: An approach for structural pattern composition. In: Lumpe, M., Vanderperren, W. (eds.) SC 2007. LNCS, vol. 4829, pp. 252–265. Springer, Heidelberg (2007) 8. Heineman, G.T., Councill, W.T. (eds.): Component-Based Software Engineering: Putting the Pieces Together. Addison-Wesley, Reading (2001) 9. Janneck, J.W., Naedele, M.: Modeling hierarchical and recursive structures using parametric Petri nets. In: Proc. Adv. Simulation Tech. Conf., pp. 445–452 (1999) 10. J¨ arvinen, H.-M., et al.: Object-oriented specification of reactive systems. In: Proc. ICSE 1990, pp. 63–71. IEEE Computer Society Press, Los Alamitos (1990) 11. Jensen, K.: Coloured Petri Nets: Basic Concepts, Analysis Methods and Practical Use, 2nd edn., vol. I. Springer, Heidelberg (1996) 12. The Java Modeling Language, http://www.cs.iastate.edu/~ leavens/JML.html 13. Lau, K.-K., et al.: Composite connectors for composing software components. In: Lumpe, M., Vanderperren, W. (eds.) SC 2007. LNCS, vol. 4829, pp. 18–33. Springer, Heidelberg (2007)
(Behavioural) Design Patterns as Composition Operators
251
14. Lau, K.-K., Ntalamagkas, I.: A compositional approach to active and passive components. In: Proc. 34th EUROMICRO SEAA, pp. 76–83. IEEE, Los Alamitos (2008) 15. Lau, K.-K., Ornaghi, M., Wang, Z.: A software component model and its preliminary formalisation. In: Proc. 4th FMCO, pp. 1–21. Springer, Heidelberg (2006) 16. Lau, K.-K., Ornaghi, M.: Control encapsulation: a calculus for exogenous composition. In: Lewis, G.A., Poernomo, I., Hofmeister, C. (eds.) CBSE 2009. LNCS, vol. 5582, pp. 121–139. Springer, Heidelberg (2009) 17. Lau, K.-K., Taweel, F.: Data encapsulation in software components. In: Proc. 10th CBSE, pp. 1–16. Springer, Heidelberg (2007) 18. Lau, K.-K., Velasco Elizondo, P., Wang, Z.: Exogenous connectors for software components. In: Proc. 8th CBSE, pp. 90–106. Springer, Heidelberg (2005) 19. Lau, K.-K., Wang, Z.: Software component models. IEEE Trans. Software Engineering 33(10), 709–724 (2007) 20. Medvidovic, N., Rosenblum, D.S., Taylor, R.N.: A type theory for software architectures. Tech. Report UCI-ICS-98-14, University of California, Irvine (1998) 21. Medvidovic, N., Taylor, R.N.: A classification and comparison framework for software architecture description languages. IEEE TSE 26(1), 70–93 (2000) 22. Message Passing Interface (MPI) Forum, http://www.mpi-forum.org/ 23. Meyer, B., Arnout, K.: Componentization: The visitor example. IEEE Computer 39(7), 23–30 (2006) 24. Mikkonen, T.: Formalizing design patterns. In: Proc. ICSE 1998, pp. 115–124. IEEE Computer Society, USA (1998) 25. OMG. UML 2.0 Infrastructure Final Adopted Spec. (2003) 26. OMG. Object Constraint Language, OCL (2006) 27. Riehle, D.: Composite design patterns. In: Proc. OOPSLA 1997, USA, pp. 218–228. ACM, New York (1997) 28. Russell, N., et al.: Workflow control-flow patterns: A revised view. BPM Center Report BPM-06-31 (2006) 29. Sun Microsystems. JavaBeans Specification (1997), http://java.sun.com/products/javabeans/docs/spec.html 30. Szyperski, C.: Universe of composition. Software Development (2002) 31. Tsiatsoulis, Z., Cotronis, J.Y.: Testing and debugging message passing programs in synergy with their specifications. Fundamenta Informatica 41(3), 341–366 (2000) 32. Velasco Elizondo, P., Lau, K.-K.: A catalogue of component connectors to support development with reuse. Journal of Systems and Software (2010) 33. Vlissides, J.: Composite design patterns (They Aren’t What You Think). C++ report (1998) 34. Wydaeghe, B., Vanderperren, W.: Visual component composition using composition patterns. In: Proc. TOOLS 2001, pp. 120–129. IEEE Computer Society, Los Alamitos (2001) 35. Yacoub, S.M., Ammar, H.H.: UML support for designing software systems as a composition of design patterns. In: Gogolla, M., Kobryn, C. (eds.) UML 2001. LNCS, vol. 2185, p. 149. Springer, Heidelberg (2001)
Author Index
Allier, Simon 216 Atkinson, Colin 166
Li, Yan 113 Loiret, Fr´ed´eric
Bureˇs, Tom´ aˇs
Malohlava, Michal 21 Mei, Hong 113 Mezini, Mira 200 Mirandola, Raffaela 1 Mitschke, Ralf 200
21
Coupaye, Thierry
74
Delaval, Gwena¨el 93 Dietrich, Jens 150, 182 Donsez, Didier 130 Duchien, Laurence 37 Eichberg, Michael Filieri, Antonio
200 1
37
Ntalamagkas, Ioannis
232
Pettersson, Paul 55 Pop, Tom´ aˇs 21 Rana, Tauseef 232 Rutten, Eric 93
Gama, Kiev 130 Ghezzi, Carlo 1 Grassi, Vincenzo 1 Guesgen, Hans W. 182 Hnˇetynka, Petr 21 Hoˇsek, Petr 21 Hummel, Oliver 166
Sadou, Salah 216 Sahraoui, Houari A. 216 Seceleanu, Cristina 55 Seinturier, Lionel 37 Servat, David 37 Stewart, Lucia 150 Suryadevara, Jagadish 55
Jenson, Graham
Tran, Cuong M.
Kang, Eun-Young Klose, Karl 200
182 55
Lau, Kung-Kiu 232 Ledoux, Thomas 74 L´eger, Marc 74
232
Vaucher, St´ephane Yang, Guo You, Chao
113 113
Zhou, Minghui
113
216