com.vmware.photon.controller.apife.backends.NetworkXenonBackend.java Source code

Java tutorial

Introduction

Here is the source code for com.vmware.photon.controller.apife.backends.NetworkXenonBackend.java

Source

/*
 * Copyright 2015 VMware, Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License.  You may obtain a copy of
 * the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed
 * under the License is distributed on an "AS IS" BASIS, without warranties or
 * conditions of any kind, EITHER EXPRESS OR IMPLIED.  See the License for the
 * specific language governing permissions and limitations under the License.
 */

package com.vmware.photon.controller.apife.backends;

import com.vmware.photon.controller.api.common.exceptions.external.ExternalException;
import com.vmware.photon.controller.api.common.exceptions.external.PageExpiredException;
import com.vmware.photon.controller.api.model.Host;
import com.vmware.photon.controller.api.model.Operation;
import com.vmware.photon.controller.api.model.ResourceList;
import com.vmware.photon.controller.api.model.Subnet;
import com.vmware.photon.controller.api.model.SubnetCreateSpec;
import com.vmware.photon.controller.api.model.SubnetState;
import com.vmware.photon.controller.api.model.Vm;
import com.vmware.photon.controller.apife.backends.clients.ApiFeXenonRestClient;
import com.vmware.photon.controller.apife.config.PaginationConfig;
import com.vmware.photon.controller.apife.entities.NetworkEntity;
import com.vmware.photon.controller.apife.entities.TaskEntity;
import com.vmware.photon.controller.apife.exceptions.external.InvalidNetworkStateException;
import com.vmware.photon.controller.apife.exceptions.external.NetworkNotFoundException;
import com.vmware.photon.controller.apife.exceptions.external.PortGroupRepeatedInMultipleNetworksException;
import com.vmware.photon.controller.apife.exceptions.external.PortGroupsAlreadyAddedToSubnetException;
import com.vmware.photon.controller.apife.exceptions.external.PortGroupsDoNotExistException;
import com.vmware.photon.controller.apife.utils.PaginationUtils;
import com.vmware.photon.controller.cloudstore.xenon.entity.NetworkService;
import com.vmware.photon.controller.cloudstore.xenon.entity.NetworkServiceFactory;
import com.vmware.photon.controller.common.xenon.ServiceUtils;
import com.vmware.photon.controller.common.xenon.exceptions.DocumentNotFoundException;
import com.vmware.xenon.common.ServiceDocumentQueryResult;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.ImmutableMap;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Perform network related operations using Xenon based cloud store.
 */
@Singleton
public class NetworkXenonBackend implements NetworkBackend {

    private static final ObjectMapper objectMapper = new ObjectMapper();

    private static final Logger logger = LoggerFactory.getLogger(NetworkXenonBackend.class);

    private final ApiFeXenonRestClient xenonClient;

    private final TaskBackend taskBackend;
    private final VmBackend vmBackend;
    private final HostBackend hostBackend;
    private final TombstoneBackend tombstoneBackend;

    @Inject
    public NetworkXenonBackend(ApiFeXenonRestClient xenonClient, TaskBackend taskBackend, VmBackend vmBackend,
            HostBackend hostBackend, TombstoneBackend tombstoneBackend) {
        this.taskBackend = taskBackend;
        this.vmBackend = vmBackend;
        this.hostBackend = hostBackend;
        this.tombstoneBackend = tombstoneBackend;

        this.xenonClient = xenonClient;
        this.xenonClient.start();
    }

    @Override
    public TaskEntity createNetwork(SubnetCreateSpec network) throws ExternalException {
        checkPortGroupsExist(network.getPortGroups());
        checkPortGroupsNotAddedToAnySubnet(network.getPortGroups());

        NetworkService.State state = new NetworkService.State();
        state.name = network.getName();
        state.description = network.getDescription();
        state.portGroups = network.getPortGroups();
        state.state = SubnetState.READY;

        com.vmware.xenon.common.Operation result = xenonClient.post(NetworkServiceFactory.SELF_LINK, state);

        NetworkService.State createdState = result.getBody(NetworkService.State.class);

        String id = ServiceUtils.getIDFromDocumentSelfLink(createdState.documentSelfLink);

        NetworkEntity networkEntity = new NetworkEntity();
        networkEntity.setId(id);
        networkEntity.setDescription(createdState.description);
        networkEntity.setPortGroups(getPortGroupsJSONString(createdState.portGroups));
        return taskBackend.createCompletedTask(networkEntity, Operation.CREATE_NETWORK);
    }

    @Override
    public ResourceList<Subnet> filter(Optional<String> name, Optional<String> portGroup,
            Optional<Integer> pageSize) {
        ServiceDocumentQueryResult queryResult = filterServiceDocuments(name, portGroup, pageSize);

        return PaginationUtils.xenonQueryResultToResourceList(NetworkService.State.class, queryResult,
                state -> toApiRepresentation(convertToEntity(state)));
    }

    @Override
    public NetworkService.State filterNetworkByPortGroup(Optional<String> portGroup)
            throws PortGroupRepeatedInMultipleNetworksException {
        ServiceDocumentQueryResult queryResult = filterServiceDocuments(Optional.absent(), portGroup,
                Optional.absent());

        ResourceList<NetworkService.State> networksList = PaginationUtils
                .xenonQueryResultToResourceList(NetworkService.State.class, queryResult);

        if (networksList == null || networksList.getItems() == null || networksList.getItems().size() == 0) {
            return null;
        } else if (networksList.getItems().size() > 1) {
            Map<String, List<NetworkService.State>> violations = new HashMap<>();
            violations.put(portGroup.toString(), networksList.getItems());
            throw new PortGroupRepeatedInMultipleNetworksException(violations);
        } else {
            return networksList.getItems().get(0);
        }
    }

    @Override
    public NetworkEntity findById(String id) throws NetworkNotFoundException {
        return convertToEntity(getById(id));
    }

    @Override
    public void tombstone(NetworkEntity network) throws ExternalException {
        List<Vm> vmsOnNetwork = vmBackend.filterByNetwork(network.getId());
        if (!vmsOnNetwork.isEmpty()) {
            logger.info("There are {} VMs still on network {}", vmsOnNetwork.size(), network.getId());
            return;
        }

        xenonClient.delete(NetworkServiceFactory.SELF_LINK + "/" + network.getId(), new NetworkService.State());
        tombstoneBackend.create(Subnet.KIND, network.getId());
        logger.info("network {} tombstoned", network.getId());
    }

    @Override
    public Subnet toApiRepresentation(String id) throws NetworkNotFoundException {
        return toApiRepresentation(convertToEntity(getById(id)));
    }

    @Override
    public TaskEntity prepareNetworkDelete(String id) throws ExternalException {
        NetworkEntity network = convertToEntity(getById(id));
        if (SubnetState.PENDING_DELETE.equals(network.getState())) {
            throw new InvalidNetworkStateException(String
                    .format("Invalid operation to delete network %s in state PENDING_DELETE", network.getId()));
        }

        NetworkService.State networkState = new NetworkService.State();
        networkState.state = SubnetState.PENDING_DELETE;
        networkState.deleteRequestTime = System.currentTimeMillis();
        this.patchNetworkService(id, networkState);
        this.tombstone(network);

        return taskBackend.createCompletedTask(network, Operation.DELETE_NETWORK);
    }

    @Override
    public TaskEntity updatePortGroups(String id, List<String> portGroups) throws ExternalException {
        NetworkService.State network = getById(id);
        checkPortGroupsExist(portGroups);
        checkPortGroupsNotAddedToAnySubnet(getPortGroupDelta(network, portGroups));

        NetworkService.State networkStateUpdate = new NetworkService.State();
        networkStateUpdate.portGroups = portGroups;
        patchNetworkService(id, networkStateUpdate);

        TaskEntity task = taskBackend.createCompletedTask(convertToEntity(network), Operation.SET_PORT_GROUPS);
        return task;
    }

    @Override
    public TaskEntity setDefault(String networkId) throws ExternalException {
        NetworkService.State currentDefaultNetwork = getDefaultNetwork();

        if (currentDefaultNetwork != null) {
            NetworkService.State currentDefaultNetworkPatch = new NetworkService.State();
            currentDefaultNetworkPatch.isDefault = false;

            try {
                xenonClient.patch(currentDefaultNetwork.documentSelfLink, currentDefaultNetworkPatch);
            } catch (DocumentNotFoundException ex) {
                throw new NetworkNotFoundException(
                        "Failed to patch current default network " + currentDefaultNetwork.documentSelfLink);
            }
        }

        NetworkService.State newDefaultNetwork = getById(networkId);
        NetworkService.State newDefaultNetworkPatch = new NetworkService.State();
        newDefaultNetworkPatch.isDefault = true;
        try {
            newDefaultNetwork = xenonClient.patch(newDefaultNetwork.documentSelfLink, newDefaultNetworkPatch)
                    .getBody(NetworkService.State.class);
        } catch (DocumentNotFoundException ex) {
            throw new NetworkNotFoundException(
                    "Failed to patch new default network " + newDefaultNetwork.documentSelfLink);
        }

        return taskBackend.createCompletedTask(convertToEntity(newDefaultNetwork), Operation.SET_DEFAULT_NETWORK);
    }

    @Override
    public NetworkEntity getDefault() throws NetworkNotFoundException {
        NetworkService.State networkState = getDefaultNetwork();
        if (networkState == null) {
            throw new NetworkNotFoundException("default (physical)");
        }

        return convertToEntity(networkState);
    }

    @Override
    public ResourceList<Subnet> getPage(String pageLink) throws ExternalException {
        ServiceDocumentQueryResult queryResult = null;
        try {
            queryResult = xenonClient.queryDocumentPage(pageLink);
        } catch (DocumentNotFoundException e) {
            throw new PageExpiredException(pageLink);
        }

        return PaginationUtils.xenonQueryResultToResourceList(NetworkService.State.class, queryResult,
                state -> toApiRepresentation(convertToEntity(state)));
    }

    private void checkPortGroupsExist(List<String> portGroups) throws PortGroupsDoNotExistException {
        List<String> missingPortGroups = new ArrayList<>();

        for (String portGroup : portGroups) {
            ResourceList<Host> hosts = this.hostBackend.filterByPortGroup(portGroup,
                    Optional.of(PaginationConfig.DEFAULT_DEFAULT_PAGE_SIZE));
            if (hosts.getItems().isEmpty()) {
                missingPortGroups.add(portGroup);
            }
        }

        if (!missingPortGroups.isEmpty()) {
            throw new PortGroupsDoNotExistException(missingPortGroups);
        }
    }

    private void checkPortGroupsNotAddedToAnySubnet(List<String> portGroups)
            throws PortGroupsAlreadyAddedToSubnetException {
        Map<String, Subnet> violations = new HashMap<>();
        for (String portGroup : portGroups) {
            ResourceList<Subnet> subnets = filter(Optional.<String>absent(), Optional.of(portGroup),
                    Optional.of(PaginationConfig.DEFAULT_DEFAULT_PAGE_SIZE));
            if (!subnets.getItems().isEmpty()) {
                violations.put(portGroup, subnets.getItems().get(0));
            }
        }

        if (!violations.isEmpty()) {
            throw new PortGroupsAlreadyAddedToSubnetException(violations);
        }
    }

    private List<String> getPortGroupDelta(NetworkService.State networkState, List<String> portGroups) {
        List<String> delta = new ArrayList<>();
        for (String portGroup : portGroups) {
            if (networkState.portGroups.contains(portGroup)) {
                continue;
            }

            delta.add(portGroup);
        }

        return delta;
    }

    private void patchNetworkService(String id, NetworkService.State networkState) throws NetworkNotFoundException {
        try {
            xenonClient.patch(NetworkServiceFactory.SELF_LINK + "/" + id, networkState);
        } catch (DocumentNotFoundException e) {
            throw new NetworkNotFoundException(id);
        }
    }

    private String getPortGroupsJSONString(List<String> portGroups) {
        try {
            return objectMapper.writeValueAsString(portGroups);
        } catch (JsonProcessingException e) {
            throw new IllegalArgumentException("Error serializing portGroups", e);
        }
    }

    private List<String> getPortGroupsFromJSONString(String portGroupsJSONString) {
        try {
            return objectMapper.readValue(portGroupsJSONString, new TypeReference<List<String>>() {
            });
        } catch (IOException ex) {
            throw new IllegalArgumentException("Error deserializing portGroups " + portGroupsJSONString, ex);
        }
    }

    private NetworkService.State getById(String id) throws NetworkNotFoundException {
        try {
            com.vmware.xenon.common.Operation result = xenonClient.get(NetworkServiceFactory.SELF_LINK + "/" + id);
            return result.getBody(NetworkService.State.class);
        } catch (DocumentNotFoundException exception) {
            throw new NetworkNotFoundException(id);
        }
    }

    private NetworkService.State getDefaultNetwork() {
        ImmutableMap.Builder<String, String> termsBuilder = new ImmutableBiMap.Builder<>();
        termsBuilder.put("isDefault", Boolean.TRUE.toString());

        List<NetworkService.State> defaultNetworks = xenonClient.queryDocuments(NetworkService.State.class,
                termsBuilder.build());

        if (defaultNetworks != null && !defaultNetworks.isEmpty()) {
            return defaultNetworks.iterator().next();
        } else {
            return null;
        }
    }

    private NetworkEntity convertToEntity(NetworkService.State network) {
        NetworkEntity networkEntity = new NetworkEntity();
        networkEntity.setName(network.name);
        networkEntity.setDescription(network.description);
        networkEntity.setState(network.state);
        networkEntity.setPortGroups(getPortGroupsJSONString(network.portGroups));
        networkEntity.setIsDefault(network.isDefault);
        networkEntity.setId(ServiceUtils.getIDFromDocumentSelfLink(network.documentSelfLink));

        return networkEntity;
    }

    private Subnet toApiRepresentation(NetworkEntity entity) {
        Subnet subnet = new Subnet();
        subnet.setId(entity.getId());
        subnet.setName(entity.getName());
        subnet.setState(entity.getState());
        subnet.setDescription(entity.getDescription());
        subnet.setPortGroups(getPortGroupsFromJSONString(entity.getPortGroups()));
        subnet.setIsDefault(entity.getIsDefault());

        return subnet;
    }

    private ServiceDocumentQueryResult filterServiceDocuments(Optional<String> name, Optional<String> portGroup,
            Optional<Integer> pageSize) {
        final ImmutableMap.Builder<String, String> termsBuilder = new ImmutableMap.Builder<>();
        if (name.isPresent()) {
            termsBuilder.put("name", name.get());
        }

        if (portGroup.isPresent()) {
            termsBuilder.put(NetworkService.PORT_GROUPS_KEY, portGroup.get().toString());
        }

        ImmutableMap<String, String> terms = termsBuilder.build();
        logger.info("Filtering networks using terms {}", terms);

        return xenonClient.queryDocuments(NetworkService.State.class, terms, pageSize, true);
    }

}