softwarecontainer  0.18.0-739e8d7 2017-05-04
softwarecontaineragent.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 <cstdint>
21 #include "softwarecontaineragent.h"
22 
23 #include "softwarecontainererror.h"
24 
25 #include "config/configerror.h"
26 #include "config/configdefinition.h"
27 #include "config/softwarecontainerconfig.h"
28 #include "capability/servicemanifestfileloader.h"
29 
30 namespace softwarecontainer {
31 
32 SoftwareContainerAgent::SoftwareContainerAgent(Glib::RefPtr<Glib::MainContext> mainLoopContext,
33  std::shared_ptr<Config> config,
34  std::shared_ptr<SoftwareContainerFactory> factory,
35  std::shared_ptr<ContainerUtilityInterface> utility):
36  m_mainLoopContext(mainLoopContext),
37  m_config(config),
38  m_factory(factory),
39  m_containerUtility(utility)
40 {
41  m_containerIdPool.push_back(0);
42 
43  // Get all configs for the config object
44  int shutdownTimeout =
45  m_config->getIntValue(ConfigDefinition::SC_GROUP,
46  ConfigDefinition::SC_SHUTDOWN_TIMEOUT_KEY);
47  std::string sharedMountsDir =
48  m_config->getStringValue(ConfigDefinition::SC_GROUP,
49  ConfigDefinition::SC_SHARED_MOUNTS_DIR_KEY);
50  std::string lxcConfigPath =
51  m_config->getStringValue(ConfigDefinition::SC_GROUP,
52  ConfigDefinition::SC_LXC_CONFIG_PATH_KEY);
53 #ifdef ENABLE_NETWORKGATEWAY
54  // All of the below are network settings
55  bool createBridge = m_config->getBoolValue(ConfigDefinition::SC_GROUP,
56  ConfigDefinition::SC_CREATE_BRIDGE_KEY);
57  std::string bridgeDevice = m_config->getStringValue(ConfigDefinition::SC_GROUP,
58  ConfigDefinition::SC_BRIDGE_DEVICE_KEY);
59  std::string bridgeIp = m_config->getStringValue(ConfigDefinition::SC_GROUP,
60  ConfigDefinition::SC_BRIDGE_IP_KEY);
61  std::string bridgeNetmask = m_config->getStringValue(ConfigDefinition::SC_GROUP,
62  ConfigDefinition::SC_BRIDGE_NETMASK_KEY);
63  int bridgeNetmaskBits = m_config->getIntValue(ConfigDefinition::SC_GROUP,
64  ConfigDefinition::SC_BRIDGE_NETMASK_BITLENGTH_KEY);
65  std::string bridgeNetAddr = m_config->getStringValue(ConfigDefinition::SC_GROUP,
66  ConfigDefinition::SC_BRIDGE_NETADDR_KEY);
67 #endif // ENABLE_NETWORKGATEWAY
68 
69  // Get all configs for the config stores
70  std::string serviceManifestDir =
71  m_config->getStringValue(ConfigDefinition::SC_GROUP,
72  ConfigDefinition::SC_SERVICE_MANIFEST_DIR_KEY);
73  std::string defaultServiceManifestDir =
74  m_config->getStringValue(ConfigDefinition::SC_GROUP,
75  ConfigDefinition::SC_DEFAULT_SERVICE_MANIFEST_DIR_KEY);
76 
77  std::unique_ptr<ServiceManifestLoader> defaultLoader(new ServiceManifestFileLoader(defaultServiceManifestDir));
78  std::unique_ptr<ServiceManifestLoader> loader(new ServiceManifestFileLoader(serviceManifestDir));
79 
80  m_filteredConfigStore = std::make_shared<FilteredConfigStore>(std::move(loader));
81  m_defaultConfigStore = std::make_shared<DefaultConfigStore>(std::move(defaultLoader));
82 
83  m_containerUtility->removeOldContainers();
84  m_containerUtility->checkWorkspace();
85 
86  m_containerConfig = SoftwareContainerConfig(
87 #ifdef ENABLE_NETWORKGATEWAY
88  createBridge,
89  bridgeDevice,
90  bridgeIp,
91  bridgeNetmask,
92  bridgeNetmaskBits,
93  bridgeNetAddr,
94 #endif // ENABLE_NETWORKGATEWAY
95  lxcConfigPath,
96  sharedMountsDir,
97  shutdownTimeout);
98 
99 }
100 
101 SoftwareContainerAgent::~SoftwareContainerAgent()
102 {
103 }
104 
105 void SoftwareContainerAgent::assertContainerExists(ContainerID containerID)
106 {
107  if (containerID >= INT32_MAX) {
108  std::string errorMessage("Invalid Container ID: "
109  + std::to_string(containerID)
110  + ". ID can not be greater than INT32");
111  log_error() << errorMessage;
112  throw SoftwareContainerError(errorMessage);
113  }
114 
115  if (containerID < 0) {
116  std::string errorMessage("Invalid Container ID: "
117  + std::to_string(containerID)
118  + ". ID can not be negative");
119  log_error() << errorMessage;
120  throw SoftwareContainerError(errorMessage);
121  }
122  if (1 > m_containers.count(containerID)) {
123  std::string errorMessage("Invalid Container ID: "
124  + std::to_string(containerID)
125  + ". No container matching that ID exists.");
126  log_error() << errorMessage;
127  throw SoftwareContainerError(errorMessage);
128  }
129 }
130 
131 std::vector<ContainerID> SoftwareContainerAgent::listContainers()
132 {
133  std::vector<ContainerID> containerIDs;
134  for (auto &cont : m_containers) {
135  containerIDs.push_back(cont.first);
136  }
137 
138  return containerIDs;
139 }
140 
141 void SoftwareContainerAgent::deleteContainer(ContainerID containerID)
142 {
143  assertContainerExists(containerID);
144 
145  m_containers.erase(containerID);
146  m_containerIdPool.push_back(containerID);
147 }
148 
149 SoftwareContainerAgent::SoftwareContainerPtr SoftwareContainerAgent::getContainer(ContainerID containerID)
150 {
151  assertContainerExists(containerID);
152  return m_containers[containerID];
153 }
154 
155 
156 ContainerID SoftwareContainerAgent::findSuitableId()
157 {
158  ContainerID availableID = m_containerIdPool.back();
159  if (m_containerIdPool.size() > 1) {
160  m_containerIdPool.pop_back();
161  } else {
162  m_containerIdPool[0]++;
163  }
164 
165  return availableID;
166 }
167 
168 ContainerID SoftwareContainerAgent::createContainer(const std::string &config)
169 {
170  profilepoint("createContainerStart");
171  profilefunction("createContainerFunction");
172 
173  // Set options for this container
174  std::unique_ptr<DynamicContainerOptions> options = m_optionParser.parse(config);
175  std::unique_ptr<SoftwareContainerConfig> containerConfig = options->toConfig(m_containerConfig);
176 
177  // Get an ID and create the container
178  ContainerID containerID = findSuitableId();
179  auto container = m_factory->createContainer(containerID, std::move(containerConfig));
180  log_debug() << "Created container with ID :" << containerID;
181 
182  m_containers[containerID] = container;
183  return containerID;
184 }
185 
186 pid_t SoftwareContainerAgent::execute(ContainerID containerID,
187  const std::string &cmdLine,
188  const std::string &workingDirectory,
189  const std::string &outputFile,
190  const EnvironmentVariables &env,
191  std::function<void (pid_t, int)> listener)
192 {
193  profilefunction("executeFunction");
194  SoftwareContainerPtr container = getContainer(containerID);
195 
196  /*
197  * We want to always apply any default capabilities we have. The only way to configure
198  * the gateways from the agent is through setCapabilities - which sets the default caps also.
199  *
200  * If it has not been set previously, then we use a call without arguments to setCapabilities
201  * to set it up, since we then should get only the default ones.
202  */
203  if (!m_containers[containerID]->previouslyConfigured()) {
204  log_info() << "Container not configured yet, configuring with default capabilities, if any";
205  GatewayConfiguration gatewayConfigs = m_defaultConfigStore->configs();
206  if (!updateGatewayConfigs(containerID, gatewayConfigs)) {
207  std::string errorMessage("Could not set default capabilities on container " + std::to_string(containerID));
208  log_error() << errorMessage;
209  throw SoftwareContainerError(errorMessage);
210  }
211 
212  }
213 
214  // Set up a CommandJob for this run in the container
215  auto job = container->createCommandJob(cmdLine);
216  if (nullptr == job) {
217  std::string errorMessage("Could not create job instance for " + cmdLine);
218  log_error() << errorMessage;
219  throw SoftwareContainerAgentError(errorMessage);
220  }
221 
222  job->setOutputFile(outputFile);
223  job->setEnvironmentVariables(env);
224  job->setWorkingDirectory(workingDirectory);
225 
226  // Start it
227  if (!job->start()) {
228  std::string errorMessage("Could not start job");
229  log_error() << errorMessage;
230  throw SoftwareContainerError(errorMessage);
231  }
232 
233  // If things went well, do what we need when it exits
234  addProcessListener(m_connections, job->pid(), [listener](pid_t pid, int exitCode) {
235  listener(pid, exitCode);
236  }, m_mainLoopContext);
237 
238  profilepoint("executeEnd");
239 
240  return job->pid();
241 }
242 
243 void SoftwareContainerAgent::shutdownContainer(ContainerID containerID)
244 {
245  profilefunction("shutdownContainerFunction");
246 
247  SoftwareContainerPtr container = getContainer(containerID);
248 
249  int timeout = m_containerConfig.containerShutdownTimeout();
250  container->shutdown(timeout);
251 
252  try {
253  deleteContainer(containerID);
254  } catch (SoftwareContainerError &err) {
255  std::string errorMessage("Could not delete the container" + std::string(err.what()));
256  log_error() << errorMessage;
257  throw SoftwareContainerError(errorMessage);
258  }
259 }
260 
261 void SoftwareContainerAgent::suspendContainer(ContainerID containerID)
262 {
263  SoftwareContainerPtr container = getContainer(containerID);
264  container->suspend();
265 }
266 
267 void SoftwareContainerAgent::resumeContainer(ContainerID containerID)
268 {
269  SoftwareContainerPtr container = getContainer(containerID);
270  container->resume();
271 }
272 
273 void SoftwareContainerAgent::bindMount(const ContainerID containerID,
274  const std::string &pathInHost,
275  const std::string &pathInContainer,
276  bool readOnly)
277 {
278  profilefunction("bindMountFunction");
279  SoftwareContainerPtr container = getContainer(containerID);
280 
281  bool result = container->bindMount(pathInHost,
282  pathInContainer,
283  readOnly);
284  if (!result) {
285  std::string errorMessage("Unable to bind mount " + pathInHost + " to " + pathInContainer);
286  log_error() << errorMessage;
287  throw SoftwareContainerError(errorMessage);
288  }
289 }
290 
291 bool SoftwareContainerAgent::updateGatewayConfigs(const ContainerID &containerID,
292  const GatewayConfiguration &configs)
293 {
294  profilefunction("updateGatewayConfigs");
295  SoftwareContainerPtr container = getContainer(containerID);
296 
297  return container->startGateways(configs);
298 }
299 
301 {
302  return m_filteredConfigStore->IDs();
303 }
304 
305 void SoftwareContainerAgent::setCapabilities(const ContainerID &containerID,
306  const std::vector<std::string> &capabilities)
307 {
308  if (capabilities.empty()) {
309  log_warning() << "Got empty list of capabilities";
310  return;
311  }
312 
313  // Log a list of all caps that were provided
314  std::string caps = "";
315  for (const std::string &capName : capabilities) {
316  caps += " " + capName;
317  }
318  log_debug() << "Will attempt to set capabilities:" + caps;
319 
320  GatewayConfiguration gatewayConfigs = m_defaultConfigStore->configs();
321  GatewayConfiguration filteredConfigs = m_filteredConfigStore->configsByID(capabilities);
322 
323  // If we get an empty config the user passed a non existent cap name
324  if (filteredConfigs.empty()) {
325  std::string errorMessage("One or more capabilities were not found");
326  log_error() << errorMessage;
327  throw SoftwareContainerError(errorMessage);
328  }
329 
330  gatewayConfigs.append(filteredConfigs);
331 
332  // Update container gateway configuration
333  if (!updateGatewayConfigs(containerID, gatewayConfigs)) {
334  std::string errorMessage("Could not set gateway configuration for capability");
335  log_error() << errorMessage;
336  throw SoftwareContainerError(errorMessage);
337  }
338 }
339 
340 } // namespace softwarecontainer
Contains the softwarecontainer::SoftwareContainerAgent class.
void addProcessListener(SignalConnectionsHandler &connections, pid_t pid, std::function< void(pid_t, int)> function, Glib::RefPtr< Glib::MainContext > context)
addProcessListener Adds a glib child watch for a process.
void setCapabilities(const ContainerID &containerID, const std::vector< std::string > &capabilities)
Set capabilities for the container.
std::vector< ContainerID > listContainers()
get a list of all containers
std::vector< std::string > listCapabilities()
List all capabilities that the user can set.
Contains all values that should be passed to SoftwareContainer on creation.
std::unique_ptr< DynamicContainerOptions > parse(const std::string &config)
Parse config needed for starting up the container in a correct manner.
ContainerID createContainer(const std::string &config)
Create a new container.
void suspendContainer(ContainerID containerID)
suspends execution of a container
pid_t execute(ContainerID containerID, const std::string &cmdLine, const std::string &workingDirectory, const std::string &outputFile, const EnvironmentVariables &env, std::function< void(pid_t, int)> listener)
Launch the given command in a the given container.
void resumeContainer(ContainerID containerID)
resumes execution of a container
void bindMount(const ContainerID containerID, const std::string &pathInHost, const std::string &pathInContainer, bool readOnly)
Bind mount a folder into the container.
SoftwareContainerPtr getContainer(ContainerID containerID)
Fetches a pointer to a SoftwareContainer matching an ID.
void shutdownContainer(ContainerID containerID)
shuts down a container
SoftwareContainerAgent(Glib::RefPtr< Glib::MainContext > mainLoopContext, std::shared_ptr< Config > config, std::shared_ptr< SoftwareContainerFactory > factory, std::shared_ptr< ContainerUtilityInterface > utility)
creates a new agent object and runs some initialization
Developers guide to adding a config item:
void deleteContainer(ContainerID containerID)
delete container by ID
An error occured in SoftwareContainerAgent.