From dcf335de6caa6528509fdf58f456230909b1c28f Mon Sep 17 00:00:00 2001 From: prachi Date: Mon, 20 Dec 2010 11:30:30 -0800 Subject: [PATCH] Intial code changes and schema changes for adding/updating hostTags. Also some code for serviceOffering. --- .../production/server/conf/components.xml | 1 + client/tomcatconf/components.xml.in | 2 +- core/src/com/cloud/agent/AgentManager.java | 13 ++- core/src/com/cloud/host/HostTagVO.java | 51 +++++++++++ core/src/com/cloud/host/HostVO.java | 18 +++- core/src/com/cloud/host/dao/DetailsDao.java | 3 + .../com/cloud/host/dao/DetailsDaoImpl.java | 32 +++++++ core/src/com/cloud/host/dao/HostDao.java | 6 +- core/src/com/cloud/host/dao/HostDaoImpl.java | 19 ++++ core/src/com/cloud/host/dao/HostTagsDao.java | 31 +++++++ .../com/cloud/host/dao/HostTagsDaoImpl.java | 65 ++++++++++++++ .../com/cloud/server/ManagementServer.java | 12 ++- .../com/cloud/service/ServiceOffering.java | 5 ++ .../com/cloud/service/ServiceOfferingVO.java | 9 +- .../com/cloud/storage/dao/StoragePoolDao.java | 2 + .../cloud/storage/dao/StoragePoolDaoImpl.java | 24 +++++ .../cloud/agent/manager/AgentManagerImpl.java | 89 +++++++++++++++---- server/src/com/cloud/api/BaseCmd.java | 3 +- .../com/cloud/api/commands/AddHostCmd.java | 11 ++- .../api/commands/AddSecondaryStorageCmd.java | 2 +- .../com/cloud/api/commands/UpdateHostCmd.java | 13 ++- .../async/executor/HostResultObject.java | 11 +++ .../cloud/server/ManagementServerImpl.java | 17 ++-- .../FirstFitStoragePoolAllocator.java | 8 +- ...HostTagBasedLocalStoragePoolAllocator.java | 82 +++++++++++++++++ setup/db/create-index-fk.sql | 1 + setup/db/create-schema.sql | 9 ++ setup/db/schema-216to217.sql | 10 +++ 28 files changed, 510 insertions(+), 39 deletions(-) create mode 100644 core/src/com/cloud/host/HostTagVO.java create mode 100644 core/src/com/cloud/host/dao/HostTagsDao.java create mode 100644 core/src/com/cloud/host/dao/HostTagsDaoImpl.java create mode 100644 server/src/com/cloud/storage/allocator/HostTagBasedLocalStoragePoolAllocator.java create mode 100644 setup/db/schema-216to217.sql diff --git a/build/deploy/production/server/conf/components.xml b/build/deploy/production/server/conf/components.xml index a40c3456ad3..a7f72cd6931 100755 --- a/build/deploy/production/server/conf/components.xml +++ b/build/deploy/production/server/conf/components.xml @@ -95,6 +95,7 @@ + diff --git a/client/tomcatconf/components.xml.in b/client/tomcatconf/components.xml.in index 49901472e1e..13a59d4bc2b 100755 --- a/client/tomcatconf/components.xml.in +++ b/client/tomcatconf/components.xml.in @@ -114,7 +114,7 @@ - + diff --git a/core/src/com/cloud/agent/AgentManager.java b/core/src/com/cloud/agent/AgentManager.java index d6c40f541d2..aa86b553307 100755 --- a/core/src/com/cloud/agent/AgentManager.java +++ b/core/src/com/cloud/agent/AgentManager.java @@ -29,6 +29,7 @@ import com.cloud.dc.PodCluster; import com.cloud.exception.AgentUnavailableException; import com.cloud.exception.DiscoveryException; import com.cloud.exception.InternalErrorException; +import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.OperationTimedoutException; import com.cloud.host.Host; import com.cloud.host.HostStats; @@ -158,8 +159,9 @@ public interface AgentManager extends Manager { * Updates a host * @param hostId * @param guestOSCategoryId + * @param hostTags */ - void updateHost(long hostId, long guestOSCategoryId); + void updateHost(long hostId, long guestOSCategoryId, String hostTags) throws UnsupportedOperationException; /** * Deletes a host @@ -209,7 +211,14 @@ public interface AgentManager extends Manager { public boolean executeUserRequest(long hostId, Event event) throws AgentUnavailableException; public boolean reconnect(final long hostId) throws AgentUnavailableException; - public List discoverHosts(long dcId, Long podId, Long clusterId, URI url, String username, String password) throws DiscoveryException; + public List discoverHosts(long dcId, Long podId, Long clusterId, URI url, String username, String password, String hostTags) throws DiscoveryException; Answer easySend(Long hostId, Command cmd, int timeout); + + /** + * Returns a comma separated list of tags for the specified host + * @param hostId + * @return comma separated list of tags + */ + String getHostTags(long hostId); } diff --git a/core/src/com/cloud/host/HostTagVO.java b/core/src/com/cloud/host/HostTagVO.java new file mode 100644 index 00000000000..3b1e898703a --- /dev/null +++ b/core/src/com/cloud/host/HostTagVO.java @@ -0,0 +1,51 @@ +package com.cloud.host; + +import javax.persistence.Column; +import javax.persistence.DiscriminatorColumn; +import javax.persistence.DiscriminatorType; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Inheritance; +import javax.persistence.InheritanceType; +import javax.persistence.Table; + +@Entity +@Table(name="host_tags") +public class HostTagVO { + @Id + @GeneratedValue(strategy=GenerationType.IDENTITY) + @Column(name="id") + private long id; + + @Column(name="host_id") + private long hostId; + + @Column(name="tag") + private String tag; + + protected HostTagVO() { + } + + public HostTagVO(long hostId, String tag) { + this.hostId = hostId; + this.tag = tag; + } + + public long getHostId() { + return hostId; + } + + public String getTag() { + return tag; + } + + public void setTag(String tag) { + this.tag = tag; + } + + public long getId() { + return id; + } +} diff --git a/core/src/com/cloud/host/HostVO.java b/core/src/com/cloud/host/HostVO.java index 6aa92ea5752..02fbbb55159 100644 --- a/core/src/com/cloud/host/HostVO.java +++ b/core/src/com/cloud/host/HostVO.java @@ -19,7 +19,7 @@ package com.cloud.host; import java.util.Date; import java.util.Map; - +import java.util.List; import javax.persistence.Column; import javax.persistence.DiscriminatorColumn; import javax.persistence.DiscriminatorType; @@ -129,7 +129,13 @@ public class HostVO implements Host { // then this field has not been loaded yet. // Call host dao to load it. @Transient - Map details; + Map details; + + // This is a delayed load value. If the value is null, + // then this field has not been loaded yet. + // Call host dao to load it. + @Transient + List hostTags; @Override public String getStorageIpAddressDeux() { @@ -271,6 +277,14 @@ public class HostVO implements Host { public void setDetails(Map details) { this.details = details; + } + + public List getHostTags() { + return hostTags; + } + + public void setHostTags(List hostTags) { + this.hostTags = hostTags; } @Column(name="data_center_id", nullable=false) diff --git a/core/src/com/cloud/host/dao/DetailsDao.java b/core/src/com/cloud/host/dao/DetailsDao.java index e398f353887..ba7a87f7f4a 100644 --- a/core/src/com/cloud/host/dao/DetailsDao.java +++ b/core/src/com/cloud/host/dao/DetailsDao.java @@ -17,6 +17,7 @@ */ package com.cloud.host.dao; +import java.util.List; import java.util.Map; import com.cloud.host.DetailVO; @@ -30,4 +31,6 @@ public interface DetailsDao extends GenericDao { DetailVO findDetail(long hostId, String name); void deleteDetails(long hostId); + + List findHostDetailsbyValue(long hostId, String value); } diff --git a/core/src/com/cloud/host/dao/DetailsDaoImpl.java b/core/src/com/cloud/host/dao/DetailsDaoImpl.java index b10bd048fc6..86e77a0a8e0 100644 --- a/core/src/com/cloud/host/dao/DetailsDaoImpl.java +++ b/core/src/com/cloud/host/dao/DetailsDaoImpl.java @@ -17,6 +17,10 @@ */ package com.cloud.host.dao; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -28,12 +32,15 @@ import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.Transaction; +import com.cloud.utils.exception.CloudRuntimeException; @Local(value=DetailsDao.class) public class DetailsDaoImpl extends GenericDaoBase implements DetailsDao { protected final SearchBuilder HostSearch; protected final SearchBuilder DetailSearch; + private final String FindHostDetailsByValue = "SELECT host_details.name FROM host_details WHERE host_id = ? and value = ?"; + protected DetailsDaoImpl() { HostSearch = createSearchBuilder(); HostSearch.and("hostId", HostSearch.entity().getHostId(), SearchCriteria.Op.EQ); @@ -77,6 +84,31 @@ public class DetailsDaoImpl extends GenericDaoBase implements De delete(result.getId()); } } + + public List findHostDetailsbyValue(long hostId, String value){ + + StringBuilder sql = new StringBuilder(FindHostDetailsByValue); + + Transaction txn = Transaction.currentTxn(); + PreparedStatement pstmt = null; + try { + pstmt = txn.prepareAutoCloseStatement(sql.toString()); + pstmt.setLong(1, hostId); + pstmt.setString(2, value); + + ResultSet rs = pstmt.executeQuery(); + List detailNames = new ArrayList(); + + while (rs.next()) { + detailNames.add(rs.getString("name")); + } + + return detailNames; + } catch (SQLException e) { + throw new CloudRuntimeException("DB Exception on: " + pstmt.toString(), e); + } + + } @Override public void persist(long hostId, Map details) { diff --git a/core/src/com/cloud/host/dao/HostDao.java b/core/src/com/cloud/host/dao/HostDao.java index 78eeea89bfb..ef775c1dccd 100644 --- a/core/src/com/cloud/host/dao/HostDao.java +++ b/core/src/com/cloud/host/dao/HostDao.java @@ -135,7 +135,7 @@ public interface HostDao extends GenericDao { long getNextSequence(long hostId); - void loadDetails(HostVO host); - - + void loadDetails(HostVO host); + + void loadHostTags(HostVO host); } diff --git a/core/src/com/cloud/host/dao/HostDaoImpl.java b/core/src/com/cloud/host/dao/HostDaoImpl.java index 83c99a241d1..a7921394061 100644 --- a/core/src/com/cloud/host/dao/HostDaoImpl.java +++ b/core/src/com/cloud/host/dao/HostDaoImpl.java @@ -87,6 +87,8 @@ public class HostDaoImpl extends GenericDaoBase implements HostDao protected final DetailsDaoImpl _detailsDao = ComponentLocator.inject(DetailsDaoImpl.class); + protected final HostTagsDaoImpl _hostTagsDao = ComponentLocator.inject(HostTagsDaoImpl.class); + public HostDaoImpl() { _vmHostDao = ComponentLocator.inject(VmHostDaoImpl.class); @@ -326,6 +328,12 @@ public class HostDaoImpl extends GenericDaoBase implements HostDao host.setDetails(details); } + @Override + public void loadHostTags(HostVO host){ + List hostTags = _hostTagsDao.gethostTags(host.getId()); + host.setHostTags(hostTags); + } + @Override public boolean updateStatus(HostVO host, Event event, long msId) { Status oldStatus = host.getStatus(); @@ -488,6 +496,14 @@ public class HostDaoImpl extends GenericDaoBase implements HostDao _detailsDao.persist(host.getId(), details); } + protected void saveHostTags(HostVO host) { + List hostTags = host.getHostTags(); + if (hostTags == null || (hostTags != null && hostTags.isEmpty())) { + return; + } + _hostTagsDao.persist(host.getId(), hostTags); + } + @Override public boolean configure(String name, Map params) throws ConfigurationException { if (!super.configure(name, params)) { @@ -509,6 +525,8 @@ public class HostDaoImpl extends GenericDaoBase implements HostDao HostVO dbHost = super.persist(host); saveDetails(host); loadDetails(dbHost); + saveHostTags(host); + loadHostTags(dbHost); txn.commit(); @@ -526,6 +544,7 @@ public class HostDaoImpl extends GenericDaoBase implements HostDao } saveDetails(host); + saveHostTags(host); txn.commit(); diff --git a/core/src/com/cloud/host/dao/HostTagsDao.java b/core/src/com/cloud/host/dao/HostTagsDao.java new file mode 100644 index 00000000000..d7ce813ee96 --- /dev/null +++ b/core/src/com/cloud/host/dao/HostTagsDao.java @@ -0,0 +1,31 @@ +/** + * Copyright (C) 2010 Cloud.com, Inc. All rights reserved. + * + * This software is licensed under the GNU General Public License v3 or later. + * + * It is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +package com.cloud.host.dao; + +import java.util.List; +import com.cloud.host.HostTagVO; +import com.cloud.utils.db.GenericDao; + +public interface HostTagsDao extends GenericDao { + + void persist(long hostId, List hostTags); + + List gethostTags(long hostId); + +} + diff --git a/core/src/com/cloud/host/dao/HostTagsDaoImpl.java b/core/src/com/cloud/host/dao/HostTagsDaoImpl.java new file mode 100644 index 00000000000..4d1738c6f31 --- /dev/null +++ b/core/src/com/cloud/host/dao/HostTagsDaoImpl.java @@ -0,0 +1,65 @@ +/** + * Copyright (C) 2010 Cloud.com, Inc. All rights reserved. + * + * This software is licensed under the GNU General Public License v3 or later. + * + * It is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +package com.cloud.host.dao; + +import java.util.ArrayList; +import java.util.List; +import javax.ejb.Local; +import com.cloud.host.HostTagVO; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.Transaction; + + +@Local(value=HostTagsDao.class) +public class HostTagsDaoImpl extends GenericDaoBase implements HostTagsDao { + protected final SearchBuilder HostSearch; + + protected HostTagsDaoImpl() { + HostSearch = createSearchBuilder(); + HostSearch.and("hostId", HostSearch.entity().getHostId(), SearchCriteria.Op.EQ); + HostSearch.done(); + } + + @Override + public List gethostTags(long hostId) { + SearchCriteria sc = HostSearch.create(); + sc.setParameters("hostId", hostId); + + List results = search(sc, null); + List hostTags = new ArrayList(results.size()); + for (HostTagVO result : results) { + hostTags.add(result.getTag()); + } + + return hostTags; + } + + @Override + public void persist(long hostId, List hostTags) { + Transaction txn = Transaction.currentTxn(); + + txn.start(); + for (String tag : hostTags) { + HostTagVO vo = new HostTagVO(hostId, tag); + persist(vo); + } + txn.commit(); + } +} diff --git a/core/src/com/cloud/server/ManagementServer.java b/core/src/com/cloud/server/ManagementServer.java index 3e12c8c5a19..7202fc1d336 100644 --- a/core/src/com/cloud/server/ManagementServer.java +++ b/core/src/com/cloud/server/ManagementServer.java @@ -281,7 +281,7 @@ public interface ManagementServer { * @return true if hosts were found; false if not. * @throws IllegalArgumentException */ - List discoverHosts(long dcId, Long podId, Long clusterId, String url, String username, String password) throws IllegalArgumentException, DiscoveryException; + List discoverHosts(long dcId, Long podId, Long clusterId, String url, String username, String password, String hostTags) throws IllegalArgumentException, DiscoveryException; String updateAdminPassword(long userId, String oldPassword, String newPassword); @@ -826,7 +826,7 @@ public interface ManagementServer { * @param hostId * @param guestOSCategoryId */ - void updateHost(long hostId, long guestOSCategoryId) throws InvalidParameterValueException; + void updateHost(long hostId, long guestOSCategoryId, String hostTags) throws InvalidParameterValueException, UnsupportedOperationException; /** * Deletes a host @@ -2182,4 +2182,12 @@ public interface ManagementServer { String getHyperType(); String getHashKey(); + + /** + * Returns a comma separated list of tags for the specified host + * @param hostId + * @return comma separated list of tags + */ + String getHostTags(long hostId); + } diff --git a/core/src/com/cloud/service/ServiceOffering.java b/core/src/com/cloud/service/ServiceOffering.java index 8160e1a3a97..c968f097ec6 100755 --- a/core/src/com/cloud/service/ServiceOffering.java +++ b/core/src/com/cloud/service/ServiceOffering.java @@ -72,5 +72,10 @@ public interface ServiceOffering { * @return whether or not the service offering requires local storage */ boolean getUseLocalStorage(); + + /* + * @return tag that should be present on the host needed ; optional parameter + */ + public String getHostTag(); } diff --git a/core/src/com/cloud/service/ServiceOfferingVO.java b/core/src/com/cloud/service/ServiceOfferingVO.java index 6dc20a91c22..49bc993c99b 100644 --- a/core/src/com/cloud/service/ServiceOfferingVO.java +++ b/core/src/com/cloud/service/ServiceOfferingVO.java @@ -54,7 +54,10 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering @Column(name="guest_ip_type") @Enumerated(EnumType.STRING) - private GuestIpType guestIpType; + private GuestIpType guestIpType; + + @Column(name="host_tag") + private String hostTag; protected ServiceOfferingVO() { super(); @@ -140,5 +143,9 @@ public class ServiceOfferingVO extends DiskOfferingVO implements ServiceOffering public GuestIpType getGuestIpType() { return guestIpType; + } + + public String getHostTag() { + return hostTag; } } diff --git a/core/src/com/cloud/storage/dao/StoragePoolDao.java b/core/src/com/cloud/storage/dao/StoragePoolDao.java index 9bbdd571927..b5b8de1b957 100644 --- a/core/src/com/cloud/storage/dao/StoragePoolDao.java +++ b/core/src/com/cloud/storage/dao/StoragePoolDao.java @@ -77,6 +77,8 @@ public interface StoragePoolDao extends GenericDao { List findPoolsByTags(long dcId, long podId, Long clusterId, String[] tags, Boolean shared); + List findPoolsByHostTags(long dcId, long podId, Long clusterId, String[] hostTags, Boolean shared); + /** * Find pool by UUID. * diff --git a/core/src/com/cloud/storage/dao/StoragePoolDaoImpl.java b/core/src/com/cloud/storage/dao/StoragePoolDaoImpl.java index e1d39bc1b53..7f0adfcf35e 100644 --- a/core/src/com/cloud/storage/dao/StoragePoolDaoImpl.java +++ b/core/src/com/cloud/storage/dao/StoragePoolDaoImpl.java @@ -303,6 +303,30 @@ public class StoragePoolDaoImpl extends GenericDaoBase imp } } + @Override + public List findPoolsByHostTags(long dcId, long podId, Long clusterId, String[] tags, Boolean shared) { + List storagePools = null; + if (tags == null || tags.length == 0) { + storagePools = listBy(dcId, podId, clusterId); + } else { + Map details = tagsToDetails(tags); + storagePools = findPoolsByDetails(dcId, podId, clusterId, details); + } + + if (shared == null) { + return storagePools; + } else { + List filteredStoragePools = new ArrayList(storagePools); + for (StoragePoolVO pool : storagePools) { + if (shared != pool.isShared()) { + filteredStoragePools.remove(pool); + } + } + + return filteredStoragePools; + } + } + @Override @DB public List searchForStoragePoolDetails(long poolId, String value){ diff --git a/server/src/com/cloud/agent/manager/AgentManagerImpl.java b/server/src/com/cloud/agent/manager/AgentManagerImpl.java index 726269df4c2..4eaae1cb8c4 100755 --- a/server/src/com/cloud/agent/manager/AgentManagerImpl.java +++ b/server/src/com/cloud/agent/manager/AgentManagerImpl.java @@ -73,6 +73,7 @@ import com.cloud.alert.AlertManager; import com.cloud.capacity.CapacityVO; import com.cloud.capacity.dao.CapacityDao; import com.cloud.configuration.Config; +import com.cloud.configuration.ConfigurationManager; import com.cloud.configuration.dao.ConfigurationDao; import com.cloud.dc.ClusterVO; import com.cloud.dc.DataCenterIpAddressVO; @@ -88,6 +89,7 @@ import com.cloud.event.dao.EventDao; import com.cloud.exception.AgentUnavailableException; import com.cloud.exception.DiscoveryException; import com.cloud.exception.InternalErrorException; +import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.OperationTimedoutException; import com.cloud.exception.UnsupportedVersionException; import com.cloud.ha.HighAvailabilityManager; @@ -145,6 +147,7 @@ import com.cloud.vm.UserVm; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VmCharacteristics; import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.host.dao.HostTagsDao; /** * Implementation of the Agent Manager. This class controls the connection to @@ -197,6 +200,7 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory { @Inject protected GuestOSCategoryDao _guestOSCategoryDao = null; @Inject protected DetailsDao _hostDetailsDao = null; @Inject protected ClusterDao _clusterDao; + @Inject protected HostTagsDao _hostTagsDao = null; private String _publicNic; private String _privateNic; @@ -219,6 +223,9 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory { @Inject protected UpgradeManager _upgradeMgr = null; + @Inject + protected ConfigurationManager _configMgr; + protected int _retry = 2; protected String _name; @@ -465,11 +472,11 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory { - protected AgentAttache handleDirectConnect(ServerResource resource, StartupCommand[] startup, Map details, boolean old) { + protected AgentAttache handleDirectConnect(ServerResource resource, StartupCommand[] startup, Map details, boolean old, List hostTags) { if (startup == null) { return null; } - HostVO server = createHost(startup, resource, details, old); + HostVO server = createHost(startup, resource, details, old, hostTags); if (server == null) { return null; } @@ -482,9 +489,12 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory { } @Override - public List discoverHosts(long dcId, Long podId, Long clusterId, URI url, String username, String password) throws IllegalArgumentException, DiscoveryException { + public List discoverHosts(long dcId, Long podId, Long clusterId, URI url, String username, String password, String hostTags) throws IllegalArgumentException, DiscoveryException { List hosts = new ArrayList(); s_logger.info("Trying to add a new host at " + url + " in data center " + dcId); + + List hostTagList = _configMgr.csvTagsToList(hostTags); + Enumeration en = _discoverers.enumeration(); while (en.hasMoreElements()) { Discoverer discoverer = en.nextElement(); @@ -493,7 +503,7 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory { for (Map.Entry> entry : resources.entrySet()) { ServerResource resource = entry.getKey(); - AgentAttache attache = simulateStart(resource, entry.getValue(), true); + AgentAttache attache = simulateStart(resource, entry.getValue(), true, hostTagList); if (attache != null) { hosts.add(_hostDao.findById(attache.getId())); } @@ -1042,7 +1052,7 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory { _executor.execute(new SimulateStartTask(host.getId(), resource, host.getDetails(), actionDelegate)); } - protected AgentAttache simulateStart(ServerResource resource, Map details, boolean old) throws IllegalArgumentException{ + protected AgentAttache simulateStart(ServerResource resource, Map details, boolean old, List hostTags) throws IllegalArgumentException{ StartupCommand[] cmds = resource.initialize(); if (cmds == null ) return null; @@ -1052,7 +1062,7 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory { s_logger.debug("Startup request from directly connected host: " + new Request(0, -1, -1, cmds, false).toString()); } try { - attache = handleDirectConnect(resource, cmds, details, old); + attache = handleDirectConnect(resource, cmds, details, old, hostTags); }catch (IllegalArgumentException ex) { s_logger.warn("Unable to connect due to ", ex); @@ -1155,6 +1165,16 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory { } } } + + @Override + public String getHostTags(long hostId){ + List hostTags = _hostTagsDao.gethostTags(hostId); + if (hostTags == null) { + return null; + } else { + return _configMgr.listToCsvTags(hostTags); + } + } @Override public String getName() { @@ -1462,7 +1482,7 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory { } } - public HostVO createHost(final StartupCommand startup, ServerResource resource, Map details, boolean directFirst) throws IllegalArgumentException { + public HostVO createHost(final StartupCommand startup, ServerResource resource, Map details, boolean directFirst, List hostTags) throws IllegalArgumentException { Host.Type type = null; if (startup instanceof StartupStorageCommand) { @@ -1522,6 +1542,7 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory { } server.setDetails(details); + server.setHostTags(hostTags); updateHost(server, startup, type, _nodeId); if (resource != null) { @@ -1570,9 +1591,9 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory { return server; } - public HostVO createHost(final StartupCommand[] startup, ServerResource resource, Map details, boolean directFirst) throws IllegalArgumentException { + public HostVO createHost(final StartupCommand[] startup, ServerResource resource, Map details, boolean directFirst, List hostTags) throws IllegalArgumentException { StartupCommand firstCmd = startup[0]; - HostVO result = createHost(firstCmd, resource, details, directFirst); + HostVO result = createHost(firstCmd, resource, details, directFirst, hostTags); if( result == null ) { return null; } @@ -1580,7 +1601,7 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory { } public AgentAttache handleConnect(final Link link, final StartupCommand[] startup) throws IllegalArgumentException { - HostVO server = createHost(startup, null, null, false); + HostVO server = createHost(startup, null, null, false, null); if ( server == null ) { return null; } @@ -1647,20 +1668,56 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory { } @Override - public void updateHost(long hostId, long guestOSCategoryId) { + public void updateHost(long hostId, long guestOSCategoryId, String hostTags) throws UnsupportedOperationException{ GuestOSCategoryVO guestOSCategory = _guestOSCategoryDao.findById(guestOSCategoryId); Map hostDetails = _hostDetailsDao.findDetails(hostId); + + boolean persistDetails = false; + String currentOSCategory = hostDetails.get("guest.os.category.id"); if (guestOSCategory != null) { - // Save a new entry for guest.os.category.id - hostDetails.put("guest.os.category.id", String.valueOf(guestOSCategory.getId())); + if(!String.valueOf(guestOSCategory.getId()).equals(currentOSCategory)){ + // Save a new entry for guest.os.category.id + hostDetails.put("guest.os.category.id", String.valueOf(guestOSCategory.getId())); + persistDetails = true; + } } else { // Delete any existing entry for guest.os.category.id - hostDetails.remove("guest.os.category.id"); + if(currentOSCategory != null){ + hostDetails.remove("guest.os.category.id"); + persistDetails = true; + } } - _hostDetailsDao.persist(hostId, hostDetails); + if(persistDetails){ + _hostDetailsDao.persist(hostId, hostDetails); + } + + //update tags + List newHostTags = _configMgr.csvTagsToList(hostTags); + List oldHostTags = _hostTagsDao.gethostTags(hostId); + + if(areExistingHostTagsRemoved(hostId, newHostTags, oldHostTags)){ + //throw error - removing the existing host tags is not allowed + throw new UnsupportedOperationException("Invalid Operation: Cannot remove existing host tags"); + } + //add the new tags to the host + newHostTags.removeAll(oldHostTags); + _hostTagsDao.persist(hostId, newHostTags); } + + private boolean areExistingHostTagsRemoved(long hostId, List newHostTags, List oldHostTags){ + boolean tagsRemoved = false; + if(newHostTags.isEmpty() && !oldHostTags.isEmpty()){ + tagsRemoved = true; + }else if(!newHostTags.isEmpty() && !oldHostTags.isEmpty()){ + if(!newHostTags.containsAll(oldHostTags)){ + tagsRemoved = true; + } + } + return tagsRemoved; + } + protected void updateHost(final HostVO host, final StartupCommand startup, final Host.Type type, final long msId) throws IllegalArgumentException { s_logger.debug("updateHost() called"); @@ -1890,7 +1947,7 @@ public class AgentManagerImpl implements AgentManager, HandlerFactory { if (s_logger.isDebugEnabled()) { s_logger.debug("Simulating start for resource " + resource.getName() + " id " + id); } - simulateStart(resource, details, false); + simulateStart(resource, details, false, null); } catch (Exception e) { s_logger.warn("Unable to simulate start on resource " + id + " name " + resource.getName(), e); } finally { diff --git a/server/src/com/cloud/api/BaseCmd.java b/server/src/com/cloud/api/BaseCmd.java index 10e5ab4f58f..6cec5b10d75 100644 --- a/server/src/com/cloud/api/BaseCmd.java +++ b/server/src/com/cloud/api/BaseCmd.java @@ -397,7 +397,8 @@ public abstract class BaseCmd { CLUSTER_NAME("clustername", BaseCmd.TYPE_STRING, "clustername"), SCOPE("scope", BaseCmd.TYPE_STRING, "scope"), SUM_ACROSS_ZONE("sumacrosszone", BaseCmd.TYPE_BOOLEAN, "sumAcrossZone"), - EXCEPTION("exception", BaseCmd.TYPE_STRING, "excetpion"); + EXCEPTION("exception", BaseCmd.TYPE_STRING, "excetpion"), + HOST_TAGS("hosttags", BaseCmd.TYPE_STRING, "hostTags"); private final String _name; private final short _dataType; diff --git a/server/src/com/cloud/api/commands/AddHostCmd.java b/server/src/com/cloud/api/commands/AddHostCmd.java index 7c154c9279d..2c9723eb4ac 100644 --- a/server/src/com/cloud/api/commands/AddHostCmd.java +++ b/server/src/com/cloud/api/commands/AddHostCmd.java @@ -48,7 +48,9 @@ public class AddHostCmd extends BaseCmd { s_properties.add(new Pair(BaseCmd.Properties.CLUSTER_NAME, Boolean.FALSE)); s_properties.add(new Pair(BaseCmd.Properties.URL, Boolean.TRUE)); s_properties.add(new Pair(BaseCmd.Properties.USERNAME, Boolean.TRUE)); - s_properties.add(new Pair(BaseCmd.Properties.PASSWORD, Boolean.TRUE)); + s_properties.add(new Pair(BaseCmd.Properties.PASSWORD, Boolean.TRUE)); + //host_tags + s_properties.add(new Pair(BaseCmd.Properties.HOST_TAGS, Boolean.FALSE)); } @Override @@ -70,6 +72,8 @@ public class AddHostCmd extends BaseCmd { String password = (String)params.get(BaseCmd.Properties.PASSWORD.getName()); Long clusterId = (Long)params.get(BaseCmd.Properties.CLUSTER_ID.getName()); String clusterName = (String)params.get(BaseCmd.Properties.CLUSTER_NAME.getName()); + //host_tags + String hostTags = (String)params.get(BaseCmd.Properties.HOST_TAGS.getName()); if (clusterName != null && clusterId != null) { throw new ServerApiException(BaseCmd.PARAM_ERROR, "Can't specify cluster by both id and name"); @@ -120,7 +124,7 @@ public class AddHostCmd extends BaseCmd { clusterId = cluster.getId(); } - List h = getManagementServer().discoverHosts(zoneId, podId, clusterId, url, username, password); + List h = getManagementServer().discoverHosts(zoneId, podId, clusterId, url, username, password, hostTags); success = !h.isEmpty(); if(success) @@ -203,6 +207,9 @@ public class AddHostCmd extends BaseCmd { // calculate memory utilized, we don't provide memory over commit serverData.add(new Pair(BaseCmd.Properties.MEMORY_USED.getName(), mem)); + + //host_tags + serverData.add(new Pair(BaseCmd.Properties.HOST_TAGS.getName(), getManagementServer().getHostTags(host.getId()))); } if (host.getType().toString().equals("Storage")) { diff --git a/server/src/com/cloud/api/commands/AddSecondaryStorageCmd.java b/server/src/com/cloud/api/commands/AddSecondaryStorageCmd.java index 8cecb968ca5..5134d43bda7 100644 --- a/server/src/com/cloud/api/commands/AddSecondaryStorageCmd.java +++ b/server/src/com/cloud/api/commands/AddSecondaryStorageCmd.java @@ -86,7 +86,7 @@ public class AddSecondaryStorageCmd extends BaseCmd { List h = null; try { - h = getManagementServer().discoverHosts(zoneId, null, null, url, null, null); + h = getManagementServer().discoverHosts(zoneId, null, null, url, null, null, null); } catch (Exception ex) { s_logger.error("Failed to add secondary storage: ", ex); throw new ServerApiException(BaseCmd.INTERNAL_ERROR, "Can't add secondary storage with url " + url); diff --git a/server/src/com/cloud/api/commands/UpdateHostCmd.java b/server/src/com/cloud/api/commands/UpdateHostCmd.java index 8c5f60cf352..35e4978d21e 100644 --- a/server/src/com/cloud/api/commands/UpdateHostCmd.java +++ b/server/src/com/cloud/api/commands/UpdateHostCmd.java @@ -50,6 +50,8 @@ public class UpdateHostCmd extends BaseCmd { static { s_properties.add(new Pair(BaseCmd.Properties.ID, Boolean.TRUE)); s_properties.add(new Pair(BaseCmd.Properties.OS_CATEGORY_ID, Boolean.FALSE)); + //host_tags + s_properties.add(new Pair(BaseCmd.Properties.HOST_TAGS, Boolean.FALSE)); } @Override @@ -69,6 +71,8 @@ public class UpdateHostCmd extends BaseCmd { public List> execute(Map params) { Long id = (Long)params.get(BaseCmd.Properties.ID.getName()); Long guestOSCategoryId = (Long)params.get(BaseCmd.Properties.OS_CATEGORY_ID.getName()); + //host_tags + String hostTags = (String)params.get(BaseCmd.Properties.HOST_TAGS.getName()); if (guestOSCategoryId == null) { guestOSCategoryId = new Long(-1); @@ -79,10 +83,15 @@ public class UpdateHostCmd extends BaseCmd { if (host == null) { throw new ServerApiException(BaseCmd.PARAM_ERROR, "Host with id " + id.toString() + " doesn't exist"); } + + List> returnValues = new ArrayList>(); try { - getManagementServer().updateHost(id, guestOSCategoryId); + getManagementServer().updateHost(id, guestOSCategoryId, hostTags); + } catch(UnsupportedOperationException uex){ + s_logger.error("Failed to update host: ", uex); + throw new ServerApiException(BaseCmd.UNSUPPORTED_ACTION_ERROR, "Failed to update host: " + uex.getMessage()); } catch (Exception ex) { s_logger.error("Failed to update host: ", ex); throw new ServerApiException(BaseCmd.INTERNAL_ERROR, "Failed to update host: " + ex.getMessage()); @@ -176,6 +185,8 @@ public class UpdateHostCmd extends BaseCmd { // calculate memory utilized, we don't provide memory over commit hostRO.setMemoryUsed(mem); + //host_tags + hostRO.setHostTags(getManagementServer().getHostTags(hostVO.getId())); } if (hostVO.getType().toString().equals("Storage")) { hostRO.setDiskSizeTotal(hostVO.getTotalSize()); diff --git a/server/src/com/cloud/async/executor/HostResultObject.java b/server/src/com/cloud/async/executor/HostResultObject.java index 652d7539c64..5f1761f1cb8 100644 --- a/server/src/com/cloud/async/executor/HostResultObject.java +++ b/server/src/com/cloud/async/executor/HostResultObject.java @@ -134,6 +134,9 @@ public class HostResultObject { @Param(name="networkkbswrite") private Long networkKbsWrite; + + @Param(name="hostTags") + private String hostTags; public long getId(){ return this.id; @@ -439,4 +442,12 @@ public class HostResultObject { public void setNetworkKbsWrite(long networkKbsWrite){ this.networkKbsWrite = networkKbsWrite; } + + public String getHostTags() { + return hostTags; + } + + public void setHostTags(String hostTags) { + this.hostTags = hostTags; + } } diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index ba1b142be3f..065fb4ed7a0 100755 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -509,7 +509,7 @@ public class ManagementServerImpl implements ManagementServer { } @Override - public List discoverHosts(long dcId, Long podId, Long clusterId, String url, String username, String password) throws IllegalArgumentException, DiscoveryException { + public List discoverHosts(long dcId, Long podId, Long clusterId, String url, String username, String password, String hostTags) throws IllegalArgumentException, DiscoveryException { URI uri; try { uri = new URI(url); @@ -517,7 +517,7 @@ public class ManagementServerImpl implements ManagementServer { throw new IllegalArgumentException("Unable to convert the url" + url, e); } // TODO: parameter checks. - return _agentMgr.discoverHosts(dcId, podId, clusterId, uri, username, password); + return _agentMgr.discoverHosts(dcId, podId, clusterId, uri, username, password, hostTags); } @Override @@ -536,6 +536,11 @@ public class ManagementServerImpl implements ManagementServer { } } + @Override + public String getHostTags(long hostId) { + return _agentMgr.getHostTags(hostId); + } + @Override public PreallocatedLunVO registerPreallocatedLun(String targetIqn, String portal, int lun, long size, long dcId, String t) { String[] tags = null; @@ -2886,17 +2891,17 @@ public class ManagementServerImpl implements ManagementServer { return _hostDao.findById(hostId); } - public void updateHost(long hostId, long guestOSCategoryId) throws InvalidParameterValueException { + public void updateHost(long hostId, long guestOSCategoryId, String hostTags) throws InvalidParameterValueException, UnsupportedOperationException { // Verify that the guest OS Category exists if (guestOSCategoryId > 0) { if (_guestOSCategoryDao.findById(guestOSCategoryId) == null) { throw new InvalidParameterValueException("Please specify a valid guest OS category."); } - } + } - _agentMgr.updateHost(hostId, guestOSCategoryId); + _agentMgr.updateHost(hostId, guestOSCategoryId, hostTags); } - + public boolean deleteHost(long hostId) { return _agentMgr.deleteHost(hostId); } diff --git a/server/src/com/cloud/storage/allocator/FirstFitStoragePoolAllocator.java b/server/src/com/cloud/storage/allocator/FirstFitStoragePoolAllocator.java index 08ce2417542..a2debb71758 100644 --- a/server/src/com/cloud/storage/allocator/FirstFitStoragePoolAllocator.java +++ b/server/src/com/cloud/storage/allocator/FirstFitStoragePoolAllocator.java @@ -52,7 +52,7 @@ public class FirstFitStoragePoolAllocator extends AbstractStoragePoolAllocator { return null; } - List pools = _storagePoolDao.findPoolsByTags(dc.getId(), pod.getId(), clusterId, dskCh.getTags(), null); + List pools = findPools(dskCh, offering, dc, pod, clusterId); if (pools.size() == 0) { if (s_logger.isDebugEnabled()) { s_logger.debug("No storage pools available for pod id : " + pod.getId()); @@ -80,4 +80,10 @@ public class FirstFitStoragePoolAllocator extends AbstractStoragePoolAllocator { return null; } } + + protected List findPools(DiskCharacteristicsTO dskCh, ServiceOffering offering, DataCenterVO dc, HostPodVO pod, Long clusterId){ + List pools = _storagePoolDao.findPoolsByTags(dc.getId(), pod.getId(), clusterId, dskCh.getTags(), null); + return pools; + + } } diff --git a/server/src/com/cloud/storage/allocator/HostTagBasedLocalStoragePoolAllocator.java b/server/src/com/cloud/storage/allocator/HostTagBasedLocalStoragePoolAllocator.java new file mode 100644 index 00000000000..30a162b2d43 --- /dev/null +++ b/server/src/com/cloud/storage/allocator/HostTagBasedLocalStoragePoolAllocator.java @@ -0,0 +1,82 @@ +/** + * Copyright (C) 2010 Cloud.com, Inc. All rights reserved. + * + * This software is licensed under the GNU General Public License v3 or later. + * + * It is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +package com.cloud.storage.allocator; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.ejb.Local; +import javax.naming.ConfigurationException; + +import org.apache.log4j.Logger; + +import com.cloud.agent.manager.allocator.HostAllocator; +import com.cloud.agent.manager.allocator.impl.FirstFitAllocator; +import com.cloud.agent.api.to.DiskCharacteristicsTO; +import com.cloud.capacity.CapacityVO; +import com.cloud.capacity.dao.CapacityDao; +import com.cloud.configuration.dao.ConfigurationDao; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.HostPodVO; +import com.cloud.host.Host; +import com.cloud.service.ServiceOffering; +import com.cloud.service.ServiceOfferingVO; +import com.cloud.service.ServiceOffering.GuestIpType; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.storage.StoragePool; +import com.cloud.storage.StoragePoolHostVO; +import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.VolumeVO; +import com.cloud.storage.dao.StoragePoolHostDao; +import com.cloud.utils.DateUtil; +import com.cloud.utils.NumbersUtil; +import com.cloud.utils.component.Inject; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.SearchCriteria.Func; +import com.cloud.vm.State; +import com.cloud.vm.UserVmVO; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VmCharacteristics; +import com.cloud.vm.dao.UserVmDao; +import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.storage.StoragePoolVO; + +// +// TODO +// Rush to make LocalStoragePoolAllocator use static allocation status, we should revisit the overall +// allocation process to make it more reliable in next release. The code put in here is pretty ugly +// +@Local(value=StoragePoolAllocator.class) +public class HostTagBasedLocalStoragePoolAllocator extends LocalStoragePoolAllocator { + private static final Logger s_logger = Logger.getLogger(HostTagBasedLocalStoragePoolAllocator.class); + + public HostTagBasedLocalStoragePoolAllocator() { + } + + @Override + protected List findPools(DiskCharacteristicsTO dskCh, ServiceOffering offering, DataCenterVO dc, HostPodVO pod, Long clusterId){ + List pools = _storagePoolDao.findPoolsByHostTags(dc.getId(), pod.getId(), clusterId, dskCh.getTags(), null); + return pools; + } +} diff --git a/setup/db/create-index-fk.sql b/setup/db/create-index-fk.sql index 44a8f8bee8a..f715491bc3b 100644 --- a/setup/db/create-index-fk.sql +++ b/setup/db/create-index-fk.sql @@ -46,6 +46,7 @@ ALTER TABLE `cloud`.`host` ADD INDEX `i_host__pod_id`(`pod_id`); ALTER TABLE `cloud`.`host` ADD CONSTRAINT `fk_host__cluster_id` FOREIGN KEY `fk_host__cluster_id`(`cluster_id`) REFERENCES `cloud`.`cluster`(`id`); ALTER TABLE `cloud`.`host_details` ADD CONSTRAINT `fk_host_details__host_id` FOREIGN KEY `fk_host_details__host_id`(`host_id`) REFERENCES `host`(`id`) ON DELETE CASCADE; +ALTER TABLE `cloud`.`host_tags` ADD CONSTRAINT `fk_host_tags__host_id` FOREIGN KEY `fk_host_tags__host_id`(`host_id`) REFERENCES `host`(`id`) ON DELETE CASCADE; ALTER TABLE `cloud`.`storage_pool` ADD CONSTRAINT `fk_storage_pool__pod_id` FOREIGN KEY `fk_storage_pool__pod_id` (`pod_id`) REFERENCES `host_pod_ref` (`id`) ON DELETE CASCADE; ALTER TABLE `cloud`.`storage_pool` ADD INDEX `i_storage_pool__pod_id`(`pod_id`); diff --git a/setup/db/create-schema.sql b/setup/db/create-schema.sql index ca561d527ef..36c193498c9 100644 --- a/setup/db/create-schema.sql +++ b/setup/db/create-schema.sql @@ -76,6 +76,7 @@ DROP TABLE IF EXISTS `cloud`.`cluster`; DROP TABLE IF EXISTS `cloud`.`netapp_volume`; DROP TABLE IF EXISTS `cloud`.`pool`; DROP TABLE IF EXISTS `cloud`.`lun`; +DROP TABLE IF EXISTS `cloud`.`host_tags`; CREATE TABLE `cloud`.`cluster` ( `id` bigint unsigned NOT NULL UNIQUE AUTO_INCREMENT COMMENT 'id', @@ -391,6 +392,13 @@ CREATE TABLE `cloud`.`op_vm_host` ( PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +CREATE TABLE `cloud`.`host_tags` ( + `id` bigint unsigned NOT NULL auto_increment, + `host_id` bigint unsigned NOT NULL COMMENT 'host id', + `tag` varchar(255) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + CREATE TABLE `cloud`.`user` ( `id` bigint unsigned NOT NULL auto_increment, `username` varchar(255) NOT NULL, @@ -784,6 +792,7 @@ CREATE TABLE `cloud`.`service_offering` ( `mc_rate` smallint unsigned default 10 COMMENT 'mcast rate throttle mbits/s', `ha_enabled` tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT 'Enable HA', `guest_ip_type` varchar(255) NOT NULL DEFAULT 'Virtualized' COMMENT 'Type of guest network -- direct or virtualized', + `host_tag` varchar(255) COMMENT 'host tag specified by the service_offering', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/setup/db/schema-216to217.sql b/setup/db/schema-216to217.sql new file mode 100644 index 00000000000..37a93f58b34 --- /dev/null +++ b/setup/db/schema-216to217.sql @@ -0,0 +1,10 @@ +CREATE TABLE `cloud`.`host_tags` ( + `id` bigint unsigned NOT NULL auto_increment, + `host_id` bigint unsigned NOT NULL COMMENT 'host id', + `tag` varchar(255) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +ALTER TABLE `cloud`.`host_tags` ADD CONSTRAINT `fk_host_tags__host_id` FOREIGN KEY `fk_host_tags__host_id`(`host_id`) REFERENCES `host`(`id`) ON DELETE CASCADE; + +ALTER TABLE `cloud`.`service_offering` ADD COLUMN `host_tag` varchar(255); \ No newline at end of file