shill: vpn: Create a VPN device when the tunnel index is available.

BUG=chromium-os:27278
TEST=unit tests

Change-Id: Ie352618d0bf276a7de2673d29620c0fe4275b0ca
Reviewed-on: https://gerrit.chromium.org/gerrit/17321
Commit-Ready: Darin Petkov <petkov@chromium.org>
Reviewed-by: Darin Petkov <petkov@chromium.org>
Tested-by: Darin Petkov <petkov@chromium.org>
diff --git a/Makefile b/Makefile
index 4e6b25f..b068a48 100644
--- a/Makefile
+++ b/Makefile
@@ -177,6 +177,7 @@
 	rpc_task.o \
 	rpc_task_dbus_adaptor.o \
 	technology.o \
+	vpn.o \
 	vpn_provider.o \
 	vpn_service.o \
 	wifi.o \
@@ -286,6 +287,7 @@
 	testrunner.o \
 	vpn_provider_unittest.o \
 	vpn_service_unittest.o \
+	vpn_unittest.o \
 	wifi_endpoint_unittest.o \
 	wifi_service_unittest.o \
 	wifi_unittest.o \
diff --git a/openvpn_driver.cc b/openvpn_driver.cc
index 69a6d33..9ffaf62 100644
--- a/openvpn_driver.cc
+++ b/openvpn_driver.cc
@@ -15,6 +15,7 @@
 #include "shill/dhcp_config.h"
 #include "shill/error.h"
 #include "shill/rpc_task.h"
+#include "shill/vpn.h"
 
 using std::map;
 using std::string;
@@ -36,12 +37,17 @@
 }  // namespace
 
 OpenVPNDriver::OpenVPNDriver(ControlInterface *control,
+                             EventDispatcher *dispatcher,
+                             Metrics *metrics,
+                             Manager *manager,
                              DeviceInfo *device_info,
                              const KeyValueStore &args)
     : control_(control),
+      dispatcher_(dispatcher),
+      metrics_(metrics),
+      manager_(manager),
       device_info_(device_info),
-      args_(args),
-      interface_index_(-1) {}
+      args_(args) {}
 
 OpenVPNDriver::~OpenVPNDriver() {}
 
@@ -53,8 +59,13 @@
 
   VLOG(2) << "Claiming " << link_name << " for OpenVPN tunnel";
 
-  // TODO(petkov): Could create a VPNDevice or DeviceStub here instead.
-  interface_index_ = interface_index;
+  CHECK(!device_);
+  device_ = new VPN(control_, dispatcher_, metrics_, manager_,
+                    link_name, interface_index);
+
+  // TODO(petkov): Allocate rpc_task_.
+
+  // TODO(petkov): Initialize options and spawn openvpn.
   return true;
 }
 
@@ -146,8 +157,12 @@
 }
 
 void OpenVPNDriver::Connect(Error *error) {
-  // TODO(petkov): Allocate rpc_task_.
-  error->Populate(Error::kNotSupported);
+  if (!device_info_->CreateTunnelInterface(&tunnel_interface_)) {
+    Error::PopulateAndLog(
+        error, Error::kInternalError, "Could not create tunnel interface.");
+    return;
+  }
+  // Wait for the ClaimInterface callback to continue the connection process.
 }
 
 void OpenVPNDriver::InitOptions(vector<string> *options, Error *error) {
@@ -168,13 +183,7 @@
   options->push_back("--persist-key");
   options->push_back("--persist-tun");
 
-  if (tunnel_interface_.empty() &&
-      !device_info_->CreateTunnelInterface(&tunnel_interface_)) {
-    Error::PopulateAndLog(
-        error, Error::kInternalError, "Could not create tunnel interface.");
-    return;
-  }
-
+  CHECK(!tunnel_interface_.empty());
   options->push_back("--dev");
   options->push_back(tunnel_interface_);
   options->push_back("--dev-type");
diff --git a/openvpn_driver.h b/openvpn_driver.h
index 00f7eab..bcefc36 100644
--- a/openvpn_driver.h
+++ b/openvpn_driver.h
@@ -14,6 +14,7 @@
 
 #include "shill/ipconfig.h"
 #include "shill/key_value_store.h"
+#include "shill/refptr_types.h"
 #include "shill/vpn_driver.h"
 
 namespace shill {
@@ -21,12 +22,18 @@
 class ControlInterface;
 class DeviceInfo;
 class Error;
+class EventDispatcher;
+class Manager;
+class Metrics;
 class RPCTask;
 class DeviceStub;
 
 class OpenVPNDriver : public VPNDriver {
  public:
   OpenVPNDriver(ControlInterface *control,
+                EventDispatcher *dispatcher,
+                Metrics *metrics,
+                Manager *manager,
                 DeviceInfo *device_info,
                 const KeyValueStore &args);
   virtual ~OpenVPNDriver();
@@ -34,16 +41,22 @@
   bool Notify(const std::string &reason,
               const std::map<std::string, std::string> &dict);
 
-  // Inherited from VPNDriver.
+  // Inherited from VPNDriver. |Connect| initiates the VPN connection by
+  // creating a tunnel device. When the device index becomes available, this
+  // instance is notified through |ClaimInterface| and resumes the connection
+  // process by setting up and spawning an external 'openvpn' process. IP
+  // configuration settings are passed back from the external process through
+  // the |Notify| RPC service method.
+  virtual void Connect(Error *error);
   virtual bool ClaimInterface(const std::string &link_name,
                               int interface_index);
-  virtual void Connect(Error *error);
 
  private:
   friend class OpenVPNDriverTest;
   FRIEND_TEST(OpenVPNDriverTest, AppendFlag);
   FRIEND_TEST(OpenVPNDriverTest, AppendValueOption);
   FRIEND_TEST(OpenVPNDriverTest, ClaimInterface);
+  FRIEND_TEST(OpenVPNDriverTest, Connect);
   FRIEND_TEST(OpenVPNDriverTest, InitOptions);
   FRIEND_TEST(OpenVPNDriverTest, InitOptionsNoHost);
   FRIEND_TEST(OpenVPNDriverTest, ParseForeignOption);
@@ -72,11 +85,14 @@
                   std::vector<std::string> *options);
 
   ControlInterface *control_;
+  EventDispatcher *dispatcher_;
+  Metrics *metrics_;
+  Manager *manager_;
   DeviceInfo *device_info_;
   KeyValueStore args_;
   scoped_ptr<RPCTask> rpc_task_;
   std::string tunnel_interface_;
-  int interface_index_;
+  VPNRefPtr device_;
 
   DISALLOW_COPY_AND_ASSIGN(OpenVPNDriver);
 };
diff --git a/openvpn_driver_unittest.cc b/openvpn_driver_unittest.cc
index a587aa5..514ed20 100644
--- a/openvpn_driver_unittest.cc
+++ b/openvpn_driver_unittest.cc
@@ -13,8 +13,12 @@
 #include "shill/ipconfig.h"
 #include "shill/mock_adaptors.h"
 #include "shill/mock_device_info.h"
+#include "shill/mock_glib.h"
+#include "shill/mock_manager.h"
+#include "shill/mock_metrics.h"
 #include "shill/nice_mock_control.h"
 #include "shill/rpc_task.h"
+#include "shill/vpn.h"
 
 using std::map;
 using std::string;
@@ -31,7 +35,9 @@
  public:
   OpenVPNDriverTest()
       : device_info_(&control_, NULL, NULL, NULL),
-        driver_(&control_, &device_info_, args_) {}
+        manager_(&control_, &dispatcher_, &metrics_, &glib_),
+        driver_(&control_, &dispatcher_, &metrics_, &manager_, &device_info_,
+                args_) {}
 
   virtual ~OpenVPNDriverTest() {}
 
@@ -57,6 +63,10 @@
 
   NiceMockControl control_;
   MockDeviceInfo device_info_;
+  EventDispatcher dispatcher_;
+  MockMetrics metrics_;
+  MockGLib glib_;
+  MockManager manager_;
   KeyValueStore args_;
   OpenVPNDriver driver_;
 };
@@ -89,9 +99,22 @@
 
 
 TEST_F(OpenVPNDriverTest, Connect) {
-  Error error;
-  driver_.Connect(&error);
-  EXPECT_EQ(Error::kNotSupported, error.type());
+  const string kInterfaceName("tun0");
+  EXPECT_CALL(device_info_, CreateTunnelInterface(_))
+      .WillOnce(Return(false))
+      .WillOnce(DoAll(SetArgumentPointee<0>(kInterfaceName), Return(true)));
+  {
+    Error error;
+    driver_.Connect(&error);
+    EXPECT_EQ(Error::kInternalError, error.type());
+    EXPECT_TRUE(driver_.tunnel_interface_.empty());
+  }
+  {
+    Error error;
+    driver_.Connect(&error);
+    EXPECT_TRUE(error.IsSuccess());
+    EXPECT_EQ(kInterfaceName, driver_.tunnel_interface_);
+  }
 }
 
 TEST_F(OpenVPNDriverTest, Notify) {
@@ -174,8 +197,7 @@
   Error error;
   vector<string> options;
   const string kInterfaceName("tun0");
-  EXPECT_CALL(device_info_, CreateTunnelInterface(_))
-      .WillOnce(DoAll(SetArgumentPointee<0>(kInterfaceName), Return(true)));
+  driver_.tunnel_interface_ = kInterfaceName;
   driver_.InitOptions(&options, &error);
   EXPECT_TRUE(error.IsSuccess());
   EXPECT_EQ("--client", options[0]);
@@ -228,10 +250,11 @@
   driver_.tunnel_interface_ = kInterfaceName;
   const int kInterfaceIndex = 1122;
   EXPECT_FALSE(driver_.ClaimInterface(kInterfaceName + "XXX", kInterfaceIndex));
-  EXPECT_EQ(-1, driver_.interface_index_);
+  EXPECT_FALSE(driver_.device_);
 
   EXPECT_TRUE(driver_.ClaimInterface(kInterfaceName, kInterfaceIndex));
-  EXPECT_EQ(kInterfaceIndex, driver_.interface_index_);
+  ASSERT_TRUE(driver_.device_);
+  EXPECT_EQ(kInterfaceIndex, driver_.device_->interface_index());
 }
 
 }  // namespace shill
diff --git a/refptr_types.h b/refptr_types.h
index c2648ad..811c6f8 100644
--- a/refptr_types.h
+++ b/refptr_types.h
@@ -21,6 +21,10 @@
 typedef scoped_refptr<const Ethernet> EthernetConstRefPtr;
 typedef scoped_refptr<Ethernet> EthernetRefPtr;
 
+class VPN;
+typedef scoped_refptr<const VPN> VPNConstRefPtr;
+typedef scoped_refptr<VPN> VPNRefPtr;
+
 class WiFi;
 typedef scoped_refptr<const WiFi> WiFiConstRefPtr;
 typedef scoped_refptr<WiFi> WiFiRefPtr;
diff --git a/vpn.cc b/vpn.cc
new file mode 100644
index 0000000..0fa1589
--- /dev/null
+++ b/vpn.cc
@@ -0,0 +1,26 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "shill/vpn.h"
+
+using std::string;
+
+namespace shill {
+
+VPN::VPN(ControlInterface *control,
+         EventDispatcher *dispatcher,
+         Metrics *metrics,
+         Manager *manager,
+         const string &link_name,
+         int interface_index)
+    : Device(control, dispatcher, metrics, manager, link_name, "",
+             interface_index, Technology::kVPN) {}
+
+VPN::~VPN() {}
+
+bool VPN::TechnologyIs(const Technology::Identifier type) const {
+  return type == Technology::kVPN;
+}
+
+}  // namespace shill
diff --git a/vpn.h b/vpn.h
new file mode 100644
index 0000000..2201831
--- /dev/null
+++ b/vpn.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef SHILL_VPN_
+#define SHILL_VPN_
+
+#include "shill/device.h"
+
+namespace shill {
+
+class VPN : public Device {
+ public:
+  VPN(ControlInterface *control,
+      EventDispatcher *dispatcher,
+      Metrics *metrics,
+      Manager *manager,
+      const std::string &link_name,
+      int interface_index);
+
+  virtual ~VPN();
+
+  virtual bool TechnologyIs(const Technology::Identifier type) const;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(VPN);
+};
+
+}  // namespace shill
+
+#endif  // SHILL_VPN_
diff --git a/vpn_provider.cc b/vpn_provider.cc
index 0adffcb..24ac690 100644
--- a/vpn_provider.cc
+++ b/vpn_provider.cc
@@ -44,8 +44,9 @@
   const string &type = args.GetString(flimflam::kProviderTypeProperty);
   scoped_ptr<VPNDriver> driver;
   if (type == flimflam::kProviderOpenVpn) {
-    driver.reset(new OpenVPNDriver(control_interface_,
-                                   manager_->device_info(), args));
+    driver.reset(new OpenVPNDriver(
+        control_interface_, dispatcher_, metrics_, manager_,
+        manager_->device_info(), args));
   } else {
     Error::PopulateAndLog(
         error, Error::kNotSupported, "Unsupported VPN type: " + type);
diff --git a/vpn_unittest.cc b/vpn_unittest.cc
new file mode 100644
index 0000000..f4a7dac
--- /dev/null
+++ b/vpn_unittest.cc
@@ -0,0 +1,51 @@
+// Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "shill/vpn.h"
+
+#include <gtest/gtest.h>
+
+#include "shill/event_dispatcher.h"
+#include "shill/mock_glib.h"
+#include "shill/mock_manager.h"
+#include "shill/mock_metrics.h"
+#include "shill/nice_mock_control.h"
+
+namespace shill {
+
+class VPNTest : public testing::Test {
+ public:
+  VPNTest()
+      : manager_(&control_, &dispatcher_, &metrics_, &glib_),
+        vpn_(new VPN(&control_,
+                     &dispatcher_,
+                     &metrics_,
+                     &manager_,
+                     kTestDeviceName,
+                     kTestInterfaceIndex)) {}
+
+  virtual ~VPNTest() {}
+
+ protected:
+  static const char kTestDeviceName[];
+  static const int kTestInterfaceIndex;
+
+  NiceMockControl control_;
+  EventDispatcher dispatcher_;
+  MockMetrics metrics_;
+  MockGLib glib_;
+  MockManager manager_;
+
+  VPNRefPtr vpn_;
+};
+
+const char VPNTest::kTestDeviceName[] = "tun0";
+const int VPNTest::kTestInterfaceIndex = 5;
+
+TEST_F(VPNTest, TechnologyIs) {
+  EXPECT_TRUE(vpn_->TechnologyIs(Technology::kVPN));
+  EXPECT_FALSE(vpn_->TechnologyIs(Technology::kEthernet));
+}
+
+}  // namespace shill