vSphere REST API and C++ (Part 2)

This is the second part in an ongoing series looking at using the vSphere REST API and C++ with the cpp rest sdk (formerly known as “Casablanca”). In the first part we authenticated against SSO and got some information from the vCenter.

In this part we are going to refine our methods so that we can reuse our code. We will introduce some structs and functions that allow us to work with results.

Connecting

The first thing we are going to introduce is a struct that will manage our connection data. It is as follows:

struct vc_info {
  std::wstring server; // The address of the server
  std::wstring token; // The SSO token.

  http_client_config config; // The connection configuration
  credentials server_credentials; // Authentication credentials for the connection

  int last_status_code; // The last HTTP status code we received.
  std::string last_exception; // A text explanation of the last exception
};

All the members are annotated with how they will be used.

We will now assemble a ConnectAsync function which will return a task (and a pointer to a vc_info object). The code looks as follows:

task<shared_ptr<vc_info>> ConnectAsync(const wchar_t* server, const wchar_t* username, const wchar_t* password)
{
	std::shared_ptr<vc_info> pInfo = make_shared<vc_info>();
	pInfo->config.set_validate_certificates(false);
	pInfo->server_credentials = credentials(username, password);
	pInfo->config.set_credentials(pInfo->server_credentials);
	pInfo->server = server;

	http_client client(server, pInfo->config);
	http_request request(methods::POST);
	request.set_request_uri(U("/rest/com/vmware/cis/session"));

	return concurrency::create_task(client.request(request)).then([pInfo](http_response response) {
		// if status is OK then extract json.
		pInfo->last_status_code = response.status_code();
		return response.extract_json();

	}).then([pInfo](web::json::value v) {
		// get and set the token
		std::shared_ptr<vc_object> data = CheckJson(U(""), v);
		if (data) {
			std::shared_ptr<vc_property> value = get_object_property(data, L"value");
			if (value) {
				pInfo->token = value->s;
			}
		}

	}).then([pInfo](task<void> t) {
		// exception catching.
		try {
			t.get();

		}
		catch (const std::exception& e) {
			pInfo->last_exception = e.what();
			printf("Exception: %s\n", e.what());

		}

		return pInfo;
	});
}

As you can see,this function will wrap the call to /rest/com/vmware/cis/session and provision our object to hold information about the “connection”.

It takes a server name, username and password and attempts to connect, returninf a vc_info object with completed information whether it is successful or not. The task chain is similar to what we wrote in part 1, with the exception that we now store the SSO token in the vc_info object. We will use a function “CheckJson” to format the result into an object hierarchy and we will be creating this in the next section.

This is how our ConnectAsync function can be used in a synchronous fashion.

std::shared_ptr<vc_info> pInfo = ConnectAsync("my-vsphere.fqdn", L"administrator@vsphere.local", L"password").get();

Handling JSON

As most calls will return information in a JSON format, we will use the following two structs to store and organise information.The CPP Rest SDK contains some JSON classes, so when we call

return response.extract_json();

on a response, we get a

web::json::value

object that we can use as we desire. We will transform this into our own objects – vc_property and vc_object, which are defined below.

struct vc_property {
	std::wstring name;
	int type;

	// unrestricted unions not working on MSVC mean s cannot be a member :(
	union {
		bool b;
		int i;
		double d;
	};

	std::wstring s;
};

struct vc_object {
	std::vector<std::shared_ptr<vc_property>> properties;

	std::shared_ptr<vc_object> parent;
	std::vector<std::shared_ptr<vc_object>> children;
};

As noted, as unrestricted unions were/are not supported on the version of Visual Studio I wrote this on, we exclude the variable “s” from the union. Possibly fixed or working in another complier thoughI haven’t checked.

We now need a function that we will call to extrapolate our JSON and structure it so we can use it. Because objects can contain other objects we may need to call this function recursively. The function takes the name of the key/value pair, the value and the parent object.

// Properties are automatically added to parent. Don't need to be returned.
// Objects are returned to be added to the children ..
std::shared_ptr<vc_object> CheckJson(utility::string_t name, web::json::value v, std::shared_ptr<vc_object> parent = nullptr)
{
	std::shared_ptr<vc_object> result = nullptr;

	if (v.is_array()) {
		for (unsigned int i = 0; i < v.size(); ++i) {
			std::shared_ptr<vc_object> obj = CheckJson(U(""), v[i], parent);
		}

	}
	else if (v.is_object()) {
		result = make_shared<vc_object>();

		web::json::object o = v.as_object();
		std::for_each(begin(o), end(o), [&result](std::pair<utility::string_t, web::json::value> item) {
			CheckJson(item.first, item.second, result);
		});

		if (parent != nullptr)
			parent->children.push_back(result);

	}
	else {
		std::shared_ptr<vc_property> prop = std::make_shared<vc_property>();
		prop->name = name;

		if (v.is_string()) {
			prop->s = v.as_string();

		}
		else if (v.is_integer()) {
			prop->i = v.as_integer();

		}
		else if (v.is_boolean()) {
			prop->b = v.as_bool();

		}
		else if (v.is_double()) {
			prop->d = v.as_double();

		}

		if (parent)
			parent->properties.push_back(prop);
	}

	return result;
}

Note: I believe the array code won’t work at this time – I haven’t worked with any arrays to test yet – we’ll take a look at this later. It will however, handle objects as children and fill in the properties as required.

As an example, when authenticating with ConnectAsync the following information is returned:

We deal with this data with the following code:

std::shared_ptr<vc_object> data = CheckJson(U(""), data);
if (data) {
	std::shared_ptr<vc_property> value = get_object_property(data, L"value");
	if (value) {
		pInfo->token = value->s;
	}
}

The code above should be fairly straight forward – the code will transform the result to a vc_object object called data, and we get the value of the key/value pair with the key of “value” and assign it to the pInfo->token member property.

We are still missing a “get_object_property” function to make the code work. As the code to walk object properties for a specific entry will be commonly used it has been pulled out into it’s own function. This is straight forward and below:

std::shared_ptr<vc_property> get_object_property(std::shared_ptr<vc_object>& obj, const wchar_t* name)
{
	std::shared_ptr<vc_property> result = nullptr;

	if (obj != nullptr && name != 0 && wcslen(name) > 0) {
		std::vector<std::shared_ptr<vc_property>>::iterator find_result = std::find_if(begin(obj->properties), end(obj->properties), [name](std::shared_ptr<vc_property> prop) {
			return (_wcsicmp(prop->name.c_str(), name) == 0);
		});

		if (find_result != end(obj->properties)) {
			result = *find_result;
		}
	}

	return result;
}

Tasks

To do things with the API I’ve written a generic function that can send requests – this is very basic at the moment. One of the ways we can improve this later is to allow for the passing of values in a POST request. The body of the function is below:

task<std::shared_ptr<vc_task>> SendRequestAsync(web::http::method method, std::shared_ptr<vc_info> pInfo, const wchar_t* uri)
{
	std::shared_ptr<vc_task> result = make_shared<vc_task>();
	result->pServer = pInfo;

	return concurrency::create_task([pInfo, uri, method]() {

		http_client client(pInfo->server, pInfo->config);
		http_request request(method);
		request.set_request_uri(uri);

		// We need to pass the token with the request.
		request.headers().add(U("vmware-api-session-id"), pInfo->token.c_str());

		return client.request(request);

	}).then([pInfo, result](http_response response) {
		pInfo->last_status_code = response.status_code();
		result->status_code = pInfo->last_status_code;
		return response.extract_json();

	}).then([result](web::json::value v) {
		// parse the result and store.
		result->data = CheckJson(U(""), v);

	}).then([result](task<void> t) -> std::shared_ptr<vc_task> {
		try {
			t.get();

		}
		catch (const std::exception& e) {
			printf("Get exception: %s.\n", e.what());
		}

		return result;
	});
}

The function takes:

  • The type of request – i.e. GET, POST, PUT, DELETE
  • The vc_info object for the server.
  • The URI to send the request to.

It will return a vc_task object that will contain the status code and the parsed JSON from the response body. The vc_task struct is defined as:

struct vc_task {
	std::weak_ptr<vc_info> pServer;
	int status_code;
	std::string exception;
	std::shared_ptr<vc_object> data;
};

Example

Now we’ve done all of this, we can put this together and try it out – we’ll get information about the hosts. This is the pertinent section of our code:

std::shared_ptr<vc_info> pInfo = ConnectAsync(vcenter_url.c_str(), admin_username.c_str(), admin_password.c_str()).get();
if (pInfo && pInfo->last_status_code == 200) {
	wprintf(L"Connected successfully to %s.\n", vcenter_url.c_str());

	// Get hosts.
	shared_ptr<vc_task> task_results = SendRequestAsync(methods::GET, pInfo, L"/rest/vcenter/host").get();
	if (task_results->status_code == 200) {
		// Do something with the hosts ...
		if (task_results->data) {
			printf("There are %d results.\n", task_results->data->children.size());

			for_each(begin(task_results->data->children), end(task_results->data->children), [](std::shared_ptr<vc_object> host) {
				std::shared_ptr<vc_property> host_name = get_object_property(host, L"name");
				if (host_name) {
					wprintf(L"Host discovered: %s\n", host_name->s.c_str());
				}
			});
		}
	}

} else {
	// Could not connect.
	if (pInfo) {
		printf("ConnectAsync request returned code %d.\n", pInfo->last_status_code);

	} else {
		printf("Unknown error - ConnectAsync did not return server information object.\n");

	}
}

When running this against my home lab (please ignore the different DNS domains :p) I get the following output:

The code is available at the github repo – github.com/nelons/vSphereRestApi – please try it out and give me any feedback you have 🙂

Next Time

Now we’ve tidied up our structure to allow reuse we’ll look at tags and getting relevant objects.