softwarecontainer  0.18.0-739e8d7 2017-05-04
softwarecontainer.cpp
1 /*
2  * Copyright (C) 2016-2017 Pelagicore AB
3  *
4  * Permission to use, copy, modify, and/or distribute this software for
5  * any purpose with or without fee is hereby granted, provided that the
6  * above copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
9  * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
10  * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
11  * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
12  * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
13  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
14  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
15  * SOFTWARE.
16  *
17  * For further information see LICENSE
18  */
19 
20 #include "softwarecontainer.h"
21 #include "gateway/gateway.h"
22 #include "container.h"
23 
24 #ifdef ENABLE_PULSEGATEWAY
25 #include "gateway/pulsegateway.h"
26 #endif
27 
28 #ifdef ENABLE_NETWORKGATEWAY
29 #include "gateway/network/networkgateway.h"
30 #endif
31 
32 #ifdef ENABLE_DBUSGATEWAY
33 #include "gateway/dbus/dbusgateway.h"
34 #endif
35 
36 #ifdef ENABLE_DEVICENODEGATEWAY
37 #include "gateway/devicenode/devicenodegateway.h"
38 #endif
39 
40 #ifdef ENABLE_CGROUPSGATEWAY
41 #include "gateway/cgroups/cgroupsgateway.h"
42 #endif
43 
44 #ifdef ENABLE_WAYLANDGATEWAY
45 #include "gateway/waylandgateway.h"
46 #endif
47 
48 #ifdef ENABLE_ENVGATEWAY
49 #include "gateway/environment/envgateway.h"
50 #endif
51 
52 #ifdef ENABLE_FILEGATEWAY
53 #include "gateway/files/filegateway.h"
54 #endif
55 
56 namespace softwarecontainer {
57 
59  std::unique_ptr<const SoftwareContainerConfig> config):
60  m_containerID(id),
61  m_config(std::move(config)),
62  m_previouslyConfigured(false)
63 {
64  m_containerRoot = buildPath(m_config->sharedMountsDir(), "SC-" + std::to_string(id));
65  m_tmpfsSize = 100485760;
66  checkContainerRoot(m_containerRoot);
67  if (m_config->writeBufferEnabled()) {
68  if (m_config->temporaryFileSystemWriteBufferEnableds()) {
69  m_tmpfsSize = m_config->temporaryFileSystemSize();
70  }
71  tmpfsMount(m_containerRoot, m_tmpfsSize);
72  }
73 
74 #ifdef ENABLE_NETWORKGATEWAY
75  checkNetworkSettings();
76 #endif // ENABLE_NETWORKGATEWAY
77 
78  m_container = std::shared_ptr<ContainerAbstractInterface>(
79  new Container("SC-" + std::to_string(id),
80  m_config->containerConfigPath(),
81  m_containerRoot,
82  m_config->writeBufferEnabled(),
83  m_config->containerShutdownTimeout()));
84 
85  if(!init()) {
86  throw SoftwareContainerError("Could not initialize SoftwareContainer, container ID: "
87  + std::to_string(id));
88  }
89 
90  m_containerState.setValueNotify(ContainerState::READY);
91 }
92 
93 SoftwareContainer::~SoftwareContainer()
94 {
95 }
96 
97 bool SoftwareContainer::start()
98 {
99  log_debug() << "Initializing container";
100  if (!m_container->initialize()) {
101  log_error() << "Could not initialize container";
102  return false;
103  }
104 
105  log_debug() << "Creating container";
106  if (!m_container->create()) {
107  return false;
108  }
109 
110  log_debug() << "Starting container";
111  if (!m_container->start(&m_pcPid)) {
112  log_error() << "Could not start container";
113  return false;
114  }
115 
116  log_debug() << "Started container with PID " << m_pcPid;
117  return true;
118 }
119 
120 bool SoftwareContainer::init()
121 {
122  if (!start()) {
123  log_error() << "Failed to start container";
124  return false;
125  }
126 
127 #ifdef ENABLE_NETWORKGATEWAY
128  try {
129  m_gateways.push_back( std::unique_ptr<Gateway>(new NetworkGateway(
130  m_containerID,
131  m_config->bridgeDevice(),
132  m_config->bridgeIPAddress(),
133  m_config->bridgeNetmaskBitLength(),
134  m_container))
135  );
136  } catch (SoftwareContainerError &error) {
137  log_error() << "Given netmask is not appropriate for creating ip address."
138  << "It should be an unsigned value between 1 and 31";
139  return false;
140  }
141 #endif
142 
143 #ifdef ENABLE_PULSEGATEWAY
144  m_gateways.push_back( std::unique_ptr<Gateway>(new PulseGateway(m_container)) );
145 #endif
146 
147 #ifdef ENABLE_DEVICENODEGATEWAY
148  m_gateways.push_back( std::unique_ptr<Gateway>(new DeviceNodeGateway(m_container)) );
149 #endif
150 
151 #ifdef ENABLE_DBUSGATEWAY
152  m_gateways.push_back( std::unique_ptr<Gateway>(new DBusGateway( getGatewayDir(), m_container)) );
153 #endif
154 
155 #ifdef ENABLE_CGROUPSGATEWAY
156  m_gateways.push_back( std::unique_ptr<Gateway>(new CgroupsGateway(m_container)) );
157 #endif
158 
159 #ifdef ENABLE_WAYLANDGATEWAY
160  m_gateways.push_back( std::unique_ptr<Gateway>(new WaylandGateway(m_container)) );
161 #endif
162 
163 #ifdef ENABLE_ENVGATEWAY
164  m_gateways.push_back( std::unique_ptr<Gateway>(new EnvironmentGateway(m_container)) );
165 #endif
166 
167 #ifdef ENABLE_FILEGATEWAY
168  m_gateways.push_back( std::unique_ptr<Gateway>(new FileGateway(m_container)) );
169 #endif
170 
171  return true;
172 }
173 
175 {
176  assertValidState();
177 
178  if (m_containerState != ContainerState::READY) {
179  std::string message = "Invalid to start gateways on non ready container " +
180  std::string(m_container->id());
181  log_error() << message;
182  throw InvalidOperationError(message);
183  }
184 
185  if (!configureGateways(gwConfig)) {
186  log_error() << "Could not configure Gateways";
187  return false;
188  }
189 
190  if (!activateGateways()) {
191  log_error() << "Could not activate Gateways";
192  return false;
193  }
194 
195  // Keep track of if user has called this method at least once
196  m_previouslyConfigured = true;
197 
198  return true;
199 }
200 
201 bool SoftwareContainer::configureGateways(const GatewayConfiguration &gwConfig)
202 {
203  assertValidState();
204 
205  // Make sure that all the gateway ids in the given GatewayConfig can map
206  // to a gateway in SoftwareContainer
207  for (auto &id : gwConfig.ids()) {
208  bool match = false;
209  for (auto &gateway : m_gateways) {
210  if (id == gateway->id()) {
211  match = true;
212  }
213  }
214  if (!match) {
215  throw GatewayError("Could not find any gateway matching id: " + id);
216  }
217  }
218 
219  // Configure Gateways
220  for (auto &gateway : m_gateways) {
221  std::string gatewayId = gateway->id();
222 
223  json_t *config = gwConfig.config(gatewayId);
224  if (config != nullptr) {
225  log_debug() << "Configuring gateway: \"" << gatewayId << "\"";
226  log_debug() << json_dumps(config, JSON_INDENT(4));
227  try {
228  if (!gateway->setConfig(config)) {
229  log_error() << "Failed to apply gateway configuration";
230  return false;
231  }
232  } catch (GatewayError &error) {
233  /*
234  * Exceptions in gateways during configuration are fatal errors for the whole of SC
235  * as it means one or more capabilities are broken.
236  */
237  log_error() << "Fatal error when configuring gateway \""
238  << gatewayId << "\": " << error.what();
239  throw error;
240  }
241  json_decref(config);
242  }
243  }
244 
245  return true;
246 }
247 
248 bool SoftwareContainer::activateGateways()
249 {
250  for (auto &gateway : m_gateways) {
251  std::string gatewayId = gateway->id();
252 
253  try {
254  if (gateway->isConfigured()) {
255  log_debug() << "Activating gateway: \"" << gatewayId << "\"";
256 
257  if (!gateway->activate()) {
258  log_error() << "Failed to activate gateway \"" << gatewayId << "\"";
259  return false;
260  }
261  }
262  } catch (GatewayError &error) {
263  /*
264  * Exceptions in gateways during activation are fatal errors for the whole of SC
265  * as it means one or more gateways will not be active in the way one or more
266  * capabilities implies, i.e. the application environment will be in a broken state.
267  */
268  log_error() << "Fatal error when activating gateway \""
269  << gatewayId << "\" : " << error.what();
270  throw error;
271  }
272  }
273 
274  return true;
275 }
276 
278 {
279  return shutdown(m_config->containerShutdownTimeout());
280 }
281 
282 void SoftwareContainer::shutdown(unsigned int timeout)
283 {
284  assertValidState();
285 
286  log_debug() << "shutdown called";
287 
288  if (m_containerState != ContainerState::READY
289  && m_containerState != ContainerState::SUSPENDED)
290  {
291  std::string message = "Invalid to shut down container which is not ready or suspended " +
292  std::string(m_container->id());
293  log_error() << message;
294  throw InvalidOperationError(message);
295  }
296 
297  if(!shutdownGateways()) {
298  log_error() << "Could not shut down all gateways cleanly, check the log";
299  }
300 
301  if(!m_container->destroy(timeout)) {
302  std::string message = "Could not destroy the container during shutdown " +
303  std::string(m_container->id());
304  log_error() << message;
305  m_containerState.setValueNotify(ContainerState::INVALID);
306  throw ContainerError(message);
307  }
308 
309  m_containerState.setValueNotify(ContainerState::TERMINATED);
310 }
311 
313 {
314  assertValidState();
315 
316  std::string id = std::string(m_container->id());
317 
318  if (m_containerState != ContainerState::READY) {
319  std::string message = "Invalid to suspend non ready container " + id;
320  log_error() << message;
321  throw InvalidOperationError(message);
322  }
323 
324  if (!m_container->suspend()) {
325  std::string message = "Failed to suspend container " + id;
326  log_error() << message;
327  m_containerState.setValueNotify(ContainerState::INVALID);
328  throw ContainerError(message);
329  }
330 
331  log_debug() << "Suspended container " << id;
332  m_containerState.setValueNotify(ContainerState::SUSPENDED);
333 }
334 
336 {
337  assertValidState();
338  std::string id = std::string(m_container->id());
339 
340  if (m_containerState != ContainerState::SUSPENDED) {
341  std::string message = "Invalid to resume non suspended container " + id;
342  log_error() << message;
343  throw InvalidOperationError(message);
344  }
345 
346  if (!m_container->resume()) {
347  std::string message = "Failed to resume container " + id;
348  log_error() << message;
349  m_containerState.setValueNotify(ContainerState::INVALID);
350  throw ContainerError(message);
351  }
352 
353  log_debug() << "Resumed container " << id;
354  m_containerState.setValueNotify(ContainerState::READY);
355 }
356 
357 
358 bool SoftwareContainer::shutdownGateways()
359 {
360  bool status = true;
361  for (auto &gateway : m_gateways) {
362  if (gateway->isActivated()) {
363  log_debug() << "Tearing down gateway: \"" << gateway->id() << "\"";
364  if (!gateway->teardown()) {
365  log_warning() << "Could not tear down gateway cleanly: " << gateway->id();
366  status = false;
367  }
368  }
369  }
370 
371  m_gateways.clear();
372  return status;
373 }
374 
375 std::string SoftwareContainer::getContainerDir()
376 {
377  const std::string containerID = std::string(m_container->id());
378  return buildPath(m_config->sharedMountsDir(), containerID);
379 }
380 
381 std::string SoftwareContainer::getGatewayDir()
382 {
383  return buildPath(getContainerDir(), "gateways");
384 }
385 
387 {
388  return m_containerState;
389 }
390 
391 std::shared_ptr<FunctionJob> SoftwareContainer::createFunctionJob(const std::function<int()> fun)
392 {
393  assertValidState();
394 
395  if (m_containerState != ContainerState::READY) {
396  std::string message = "Invalid to execute code in non ready container " +
397  std::string(m_container->id());
398  log_error() << message;
399  throw InvalidOperationError(message);
400  }
401 
402  return std::make_shared<FunctionJob>(m_container, fun);
403 }
404 
405 std::shared_ptr<CommandJob> SoftwareContainer::createCommandJob(const std::string &command)
406 {
407  assertValidState();
408 
409  if (m_containerState != ContainerState::READY) {
410  std::string message = "Invalid to execute code in non ready container " +
411  std::string(m_container->id());
412  log_error() << message;
413  throw InvalidOperationError(message);
414  }
415 
416  return std::make_shared<CommandJob>(m_container, command);
417 }
418 
419 bool SoftwareContainer::bindMount(const std::string &pathOnHost,
420  const std::string &pathInContainer,
421  bool readonly)
422 {
423  assertValidState();
424 
425  std::string id = std::string(m_container->id());
426 
427  if (m_containerState != ContainerState::READY) {
428  std::string message = "Invalid to bind mount in non ready container " + id;
429  log_error() << message;
430  throw InvalidOperationError(message);
431  }
432 
433  return m_container->bindMountInContainer(pathOnHost, pathInContainer, readonly);
434 }
435 
436 void SoftwareContainer::assertValidState()
437 {
438  if (m_containerState == ContainerState::INVALID) {
439  std::string message = "Container is invalid " + std::string(m_container->id());
440  log_error() << message;
441  throw InvalidContainerError(message);
442  }
443 }
444 
446 {
447  return m_previouslyConfigured;
448 }
449 
450 void SoftwareContainer::checkContainerRoot(std::string rootDir)
451 {
452  if (!isDirectory(rootDir)) {
453  log_debug() << "Container root " << rootDir << " does not exist, trying to create";
454  std::unique_ptr<CreateDir> createDirInstance = std::unique_ptr<CreateDir>(new CreateDir());
455  if(!createDirInstance->createDirectory(rootDir)) {
456  std::string message = "Failed to create container root directory";
457  log_error() << message;
458  throw SoftwareContainerError(message);
459  }
460 
461  m_createDirList.push_back(std::move(createDirInstance));
462  }
463  if (!isDirectoryEmpty(rootDir)) {
464  log_warning() << "Container Root " << rootDir << " is not empty.";
465  }
466 }
467 
468 #ifdef ENABLE_NETWORKGATEWAY
469 void SoftwareContainer::checkNetworkSettings()
470 {
471  std::vector<std::string> argv;
472  argv.push_back(buildPath(std::string(INSTALL_BINDIR), "setup_softwarecontainer.sh"));
473  argv.push_back(m_config->bridgeDevice());
474 
475  if (m_config->shouldCreateBridge()) {
476  argv.push_back(m_config->bridgeIPAddress());
477  argv.push_back(m_config->bridgeNetmask());
478  argv.push_back(std::to_string(m_config->bridgeNetmaskBitLength()));
479  argv.push_back(m_config->bridgeNetAddr());
480  }
481 
482  log_debug() << "Checking the network setup...";
483  int returnCode;
484  try {
485  Glib::spawn_sync("", argv, Glib::SPAWN_DEFAULT,
486  sigc::slot<void>(), nullptr,
487  nullptr, &returnCode);
488  } catch (Glib::SpawnError e) {
489  std::string message = "Failed to spawn setup_softwarecontainer.sh: " + e.what();
490  log_error() << message;
491  throw SoftwareContainerError(message);
492  }
493 
494  if (returnCode != 0) {
495  std::string message = "Return code of setup_softwarecontainer.sh is non-zero";
496  log_error() << message;
497  throw SoftwareContainerError(message);
498  }
499 
500 }
501 #endif // ENABLE_NETWORKGATEWAY
502 
503 } // namespace softwarecontainer
std::shared_ptr< FunctionJob > createFunctionJob(const std::function< int()> fun)
Create a job that can run a function in a container.
bool isDirectoryEmpty(const std::string &path)
isDirectoryEmpty Check if path is empty
bool tmpfsMount(const std::string dst, const int maxSize)
tmpfsMount Mount a tmpfs in the dst path and limit size of the tmpfs to maxSize
A method was called which is inappropriate in the current state.
SoftwareContainer(const ContainerID id, std::unique_ptr< const SoftwareContainerConfig > config)
Creates a new SoftwareContainer instance.
Sets up and manages network access and routing to the container.
This gateway lets you map files (including socket files) or folders from the host into the container&#39;...
Definition: filegateway.h:35
Environment Gateway is used to define environment variables to the container.
Definition: envgateway.h:28
The CreateDir class is responsible for creating new directories and removing them when it is necessar...
Definition: createdir.h:33
std::vector< std::unique_ptr< CreateDir > > m_createDirList
m_createDirList A vector of CreateDir classes.
The Container class is an abstraction of the specific containment technology used.
Definition: container.h:42
bool previouslyConfigured()
Indicates if gateways have been configured previously.
bool startGateways(const GatewayConfiguration &configs)
Starts the Gateways by setting the gateway configurations and activating the configured gateway...
std::shared_ptr< CommandJob > createCommandJob(const std::string &command)
Create a job that can run a command in a container.
The cgroups gateway sets cgroups related settings for the container.
The container instance is in an invalid state and should not be used.
bool isDirectory(const std::string &path)
isDirectory Check if path is a directory
void shutdown()
Shut down the container.
An error has occured in the underlying container implementation.
void resume()
Resume a suspended container.
ObservableProperty< ContainerState > & getContainerState()
Get the state of this container instance.
void suspend()
Suspend the container.
Developers guide to adding a config item:
The PulseAudio Gateway is used to provide access to the host system PulseAudio server.
Definition: pulsegateway.h:29
bool bindMount(const std::string &pathOnHost, const std::string &pathInContainer, bool readonly=true)
Should only be called on containers in state &#39;READY&#39;.
This gateway is responsible for exposing device nodes in an LXC container.